diff --git a/packages/ui/src/components/desktop/WindowsWindowControls.tsx b/packages/ui/src/components/desktop/WindowsWindowControls.tsx
index 6b423955..900d5a70 100644
--- a/packages/ui/src/components/desktop/WindowsWindowControls.tsx
+++ b/packages/ui/src/components/desktop/WindowsWindowControls.tsx
@@ -3,8 +3,8 @@ import React, { useEffect } from 'react';
import { Icon } from '@/components/icon/Icon';
import { useI18n } from '@/lib/i18n';
import { cn } from '@/lib/utils';
-import { invokeDesktop } from '@/lib/desktop';
-import type { DesktopWindowControlsSide } from '@/lib/desktop';
+import { getDesktopWindowControlsOrder, invokeDesktop } from '@/lib/desktop';
+import type { DesktopWindowControlAction, DesktopWindowControlsSide } from '@/lib/desktop';
type WindowsWindowControlsProps = {
visible: boolean;
@@ -62,31 +62,44 @@ export const WindowsWindowControls = React.memo(function WindowsWindowControls({
? 'app-region-no-drag mr-1 flex h-8 shrink-0 items-center'
: 'app-region-no-drag ml-1 flex h-12 shrink-0 items-center';
- return (
-
-
-
+ const renderControl = (action: DesktopWindowControlAction) => {
+ if (action === 'minimize') {
+ return (
+
+ );
+ }
+
+ if (action === 'maximize') {
+ return (
+
+ );
+ }
+
+ return (
+ );
+ };
+
+ return (
+
+ {getDesktopWindowControlsOrder(position).map(renderControl)}
);
});
diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx
index 0485de00..c28f2b70 100644
--- a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx
+++ b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx
@@ -281,7 +281,6 @@ const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain'
type VisibleSetting = 'sessionAssist' | 'sessionGoal' | 'theme' | 'windowControlsPosition' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'terminalShell' | 'terminalLoginShell' | 'editorFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'collapsibleUserMessages' | 'stickyUserHeader' | 'promptNavigatorEnabled' | 'wideChatLayout' | 'codeBlockLineWrap' | 'splitAssistantMessageActions' | 'subagentReadOnlyBanner' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'followUpBehavior' | 'terminalQuickKeys' | 'fileEditorKeymap' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage' | 'expandedEditorToolbar';
const WINDOW_CONTROLS_POSITION_OPTIONS: Array<{ id: DesktopWindowControlsPosition; labelKey: string }> = [
- { id: 'auto', labelKey: 'settings.openchamber.desktopNetwork.option.windowControlsAuto' },
{ id: 'left', labelKey: 'settings.openchamber.desktopNetwork.option.windowControlsLeft' },
{ id: 'right', labelKey: 'settings.openchamber.desktopNetwork.option.windowControlsRight' },
];
diff --git a/packages/ui/src/lib/desktop.ts b/packages/ui/src/lib/desktop.ts
index c1f58a93..d8b41983 100644
--- a/packages/ui/src/lib/desktop.ts
+++ b/packages/ui/src/lib/desktop.ts
@@ -38,8 +38,9 @@ export type SkillCatalogConfig = {
gitIdentityId?: string;
};
-export type DesktopWindowControlsPosition = 'auto' | 'left' | 'right';
+export type DesktopWindowControlsPosition = 'left' | 'right';
export type DesktopWindowControlsSide = 'left' | 'right';
+export type DesktopWindowControlAction = 'close' | 'minimize' | 'maximize';
export type DesktopSettings = {
themeId?: string;
@@ -252,6 +253,9 @@ export const getElectronPlatform = (): string | null => {
/** Width of the three in-app window control buttons when placed on the left (3 × w-8). */
export const DESKTOP_WINDOW_CONTROLS_WIDTH_PX = 96;
+/** Default side for in-app window controls (Windows-style, right). */
+export const DEFAULT_DESKTOP_WINDOW_CONTROLS_POSITION: DesktopWindowControlsPosition = 'right';
+
/** Windows and Linux use frameless windows with in-app minimize/maximize/close controls. */
export const usesFramelessElectronChrome = (): boolean => {
if (!isElectronShell()) return false;
@@ -259,21 +263,36 @@ export const usesFramelessElectronChrome = (): boolean => {
return platform === 'win32' || platform === 'linux';
};
-export const getDefaultDesktopWindowControlsSide = (platform: string | null = getElectronPlatform()): DesktopWindowControlsSide => {
- if (platform === 'linux') {
- return 'left';
+/** Normalize a stored preference; legacy `auto` maps to the right-side default. */
+export const normalizeDesktopWindowControlsPosition = (
+ value: unknown,
+): DesktopWindowControlsPosition | undefined => {
+ if (value === 'left' || value === 'right') {
+ return value;
}
- return 'right';
+ // Legacy "auto" never read OS chrome config; treat it as the right default.
+ if (value === 'auto') {
+ return DEFAULT_DESKTOP_WINDOW_CONTROLS_POSITION;
+ }
+ return undefined;
};
export const resolveDesktopWindowControlsSide = (
preference: DesktopWindowControlsPosition | undefined,
- platform: string | null = getElectronPlatform(),
): DesktopWindowControlsSide => {
- if (preference === 'left' || preference === 'right') {
- return preference;
- }
- return getDefaultDesktopWindowControlsSide(platform);
+ return preference === 'left' ? 'left' : DEFAULT_DESKTOP_WINDOW_CONTROLS_POSITION;
+};
+
+/**
+ * Left matches macOS traffic-light order (close, minimize, maximize).
+ * Right keeps Windows order (minimize, maximize, close).
+ */
+export const getDesktopWindowControlsOrder = (
+ side: DesktopWindowControlsSide,
+): DesktopWindowControlAction[] => {
+ return side === 'left'
+ ? ['close', 'minimize', 'maximize']
+ : ['minimize', 'maximize', 'close'];
};
export const hasDesktopInvoke = (): boolean => {
diff --git a/packages/ui/src/lib/desktopWindowControls.test.ts b/packages/ui/src/lib/desktopWindowControls.test.ts
new file mode 100644
index 00000000..a38e0159
--- /dev/null
+++ b/packages/ui/src/lib/desktopWindowControls.test.ts
@@ -0,0 +1,32 @@
+import { describe, expect, test } from 'bun:test';
+
+import {
+ DEFAULT_DESKTOP_WINDOW_CONTROLS_POSITION,
+ getDesktopWindowControlsOrder,
+ normalizeDesktopWindowControlsPosition,
+ resolveDesktopWindowControlsSide,
+} from './desktop';
+
+describe('desktop window controls position', () => {
+ test('defaults to right', () => {
+ expect(DEFAULT_DESKTOP_WINDOW_CONTROLS_POSITION).toBe('right');
+ expect(resolveDesktopWindowControlsSide(undefined)).toBe('right');
+ expect(resolveDesktopWindowControlsSide('right')).toBe('right');
+ expect(resolveDesktopWindowControlsSide('left')).toBe('left');
+ });
+
+ test('maps legacy auto to right', () => {
+ expect(normalizeDesktopWindowControlsPosition('auto')).toBe('right');
+ expect(normalizeDesktopWindowControlsPosition('left')).toBe('left');
+ expect(normalizeDesktopWindowControlsPosition('right')).toBe('right');
+ expect(normalizeDesktopWindowControlsPosition('invalid')).toEqual(undefined);
+ });
+
+ test('left uses macOS traffic-light order', () => {
+ expect(getDesktopWindowControlsOrder('left')).toEqual(['close', 'minimize', 'maximize']);
+ });
+
+ test('right uses Windows order', () => {
+ expect(getDesktopWindowControlsOrder('right')).toEqual(['minimize', 'maximize', 'close']);
+ });
+});
diff --git a/packages/ui/src/lib/i18n/messages/en.settings.ts b/packages/ui/src/lib/i18n/messages/en.settings.ts
index da2aed99..85755baa 100644
--- a/packages/ui/src/lib/i18n/messages/en.settings.ts
+++ b/packages/ui/src/lib/i18n/messages/en.settings.ts
@@ -941,9 +941,8 @@ export const settingsDict = {
'settings.openchamber.sessionRetention.toast.failedDeleteCount': 'Failed to delete {count} session(s)',
'settings.openchamber.desktopNetwork.title': 'Desktop Network Access',
'settings.openchamber.desktopNetwork.field.windowControlsPosition': 'Window controls position',
- 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': 'Choose where minimize, maximize, and close buttons appear. Auto follows your operating system.',
+ 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': 'Choose where minimize, maximize, and close buttons appear. Defaults to the right.',
'settings.openchamber.desktopNetwork.field.windowControlsPositionAria': 'Window controls position',
- 'settings.openchamber.desktopNetwork.option.windowControlsAuto': 'Auto',
'settings.openchamber.desktopNetwork.option.windowControlsLeft': 'Left',
'settings.openchamber.desktopNetwork.option.windowControlsRight': 'Right',
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'Start OpenChamber at login',
diff --git a/packages/ui/src/lib/i18n/messages/es.settings.ts b/packages/ui/src/lib/i18n/messages/es.settings.ts
index 13421f11..ae93eb68 100644
--- a/packages/ui/src/lib/i18n/messages/es.settings.ts
+++ b/packages/ui/src/lib/i18n/messages/es.settings.ts
@@ -908,9 +908,8 @@ export const settingsDict = {
"settings.openchamber.sessionRetention.toast.failedDeleteCount": "No se pudo eliminar {count} sesión(es)",
"settings.openchamber.desktopNetwork.title": "Acceso de red de escritorio",
"settings.openchamber.desktopNetwork.field.windowControlsPosition": "Posición de los controles de ventana",
- "settings.openchamber.desktopNetwork.field.windowControlsPositionDescription": "Elige dónde aparecen los botones de minimizar, maximizar y cerrar. Automático sigue el sistema operativo.",
+ "settings.openchamber.desktopNetwork.field.windowControlsPositionDescription": "Elige dónde aparecen los botones de minimizar, maximizar y cerrar. Por defecto a la derecha.",
"settings.openchamber.desktopNetwork.field.windowControlsPositionAria": "Posición de los controles de ventana",
- "settings.openchamber.desktopNetwork.option.windowControlsAuto": "Automático",
"settings.openchamber.desktopNetwork.option.windowControlsLeft": "Izquierda",
"settings.openchamber.desktopNetwork.option.windowControlsRight": "Derecha",
"settings.openchamber.desktopNetwork.field.launchAtLoginAria": "Iniciar OpenChamber al iniciar sesión",
diff --git a/packages/ui/src/lib/i18n/messages/fr.settings.ts b/packages/ui/src/lib/i18n/messages/fr.settings.ts
index e3919233..18be1a46 100644
--- a/packages/ui/src/lib/i18n/messages/fr.settings.ts
+++ b/packages/ui/src/lib/i18n/messages/fr.settings.ts
@@ -829,9 +829,8 @@ export const settingsDict = {
'settings.openchamber.sessionRetention.toast.failedDeleteCount': 'Échec de la suppression des sessions {count}',
'settings.openchamber.desktopNetwork.title': 'Accès au réseau de bureau',
'settings.openchamber.desktopNetwork.field.windowControlsPosition': 'Position des contrôles de fenêtre',
- 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': 'Choisissez où apparaissent les boutons Réduire, Agrandir et Fermer. Auto suit le système d’exploitation.',
+ 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': 'Choisissez où apparaissent les boutons Réduire, Agrandir et Fermer. Par défaut à droite.',
'settings.openchamber.desktopNetwork.field.windowControlsPositionAria': 'Position des contrôles de fenêtre',
- 'settings.openchamber.desktopNetwork.option.windowControlsAuto': 'Auto',
'settings.openchamber.desktopNetwork.option.windowControlsLeft': 'Gauche',
'settings.openchamber.desktopNetwork.option.windowControlsRight': 'Droite',
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'Démarrez OpenChamber lors de la connexion',
diff --git a/packages/ui/src/lib/i18n/messages/ja.settings.ts b/packages/ui/src/lib/i18n/messages/ja.settings.ts
index d8cb2267..0680d9eb 100644
--- a/packages/ui/src/lib/i18n/messages/ja.settings.ts
+++ b/packages/ui/src/lib/i18n/messages/ja.settings.ts
@@ -941,9 +941,8 @@ export const settingsDict = {
'settings.openchamber.sessionRetention.toast.failedDeleteCount': '{count} 個の Session の削除に失敗しました',
'settings.openchamber.desktopNetwork.title': 'Desktop ネットワークアクセス',
'settings.openchamber.desktopNetwork.field.windowControlsPosition': 'ウィンドウコントロールの位置',
- 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': '最小化・最大化・閉じるボタンの表示位置を選びます。自動は OS に合わせます。',
+ 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': '最小化・最大化・閉じるボタンの表示位置を選びます。デフォルトは右側です。',
'settings.openchamber.desktopNetwork.field.windowControlsPositionAria': 'ウィンドウコントロールの位置',
- 'settings.openchamber.desktopNetwork.option.windowControlsAuto': '自動',
'settings.openchamber.desktopNetwork.option.windowControlsLeft': '左',
'settings.openchamber.desktopNetwork.option.windowControlsRight': '右',
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'ログイン時に OpenChamber を起動',
diff --git a/packages/ui/src/lib/i18n/messages/ko.settings.ts b/packages/ui/src/lib/i18n/messages/ko.settings.ts
index 5eb403a3..01019123 100644
--- a/packages/ui/src/lib/i18n/messages/ko.settings.ts
+++ b/packages/ui/src/lib/i18n/messages/ko.settings.ts
@@ -908,9 +908,8 @@ export const settingsDict = {
'settings.openchamber.sessionRetention.toast.failedDeleteCount': '세션 {count}개를 삭제하지 못했습니다',
'settings.openchamber.desktopNetwork.title': 'Desktop 네트워크 접속',
'settings.openchamber.desktopNetwork.field.windowControlsPosition': '창 컨트롤 위치',
- 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': '최소화, 최대화, 닫기 버튼이 표시될 위치를 선택합니다. 자동은 운영체제를 따릅니다.',
+ 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': '최소화, 최대화, 닫기 버튼이 표시될 위치를 선택합니다. 기본값은 오른쪽입니다.',
'settings.openchamber.desktopNetwork.field.windowControlsPositionAria': '창 컨트롤 위치',
- 'settings.openchamber.desktopNetwork.option.windowControlsAuto': '자동',
'settings.openchamber.desktopNetwork.option.windowControlsLeft': '왼쪽',
'settings.openchamber.desktopNetwork.option.windowControlsRight': '오른쪽',
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': '로그인 시 OpenChamber 시작',
diff --git a/packages/ui/src/lib/i18n/messages/pl.settings.ts b/packages/ui/src/lib/i18n/messages/pl.settings.ts
index 66734daa..2a60dfd4 100644
--- a/packages/ui/src/lib/i18n/messages/pl.settings.ts
+++ b/packages/ui/src/lib/i18n/messages/pl.settings.ts
@@ -776,9 +776,8 @@ export const settingsDict = {
'settings.openchamber.desktopNetwork.hint.openNow': 'Otwórz z innego urządzenia: ',
'settings.openchamber.desktopNetwork.title': 'Dostęp sieciowy pulpitu',
'settings.openchamber.desktopNetwork.field.windowControlsPosition': 'Pozycja elementów sterujących oknem',
- 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': 'Wybierz, gdzie mają się pojawiać przyciski minimalizacji, maksymalizacji i zamykania. Automatycznie dopasowuje się do systemu operacyjnego.',
+ 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': 'Wybierz, gdzie mają się pojawiać przyciski minimalizacji, maksymalizacji i zamykania. Domyślnie po prawej.',
'settings.openchamber.desktopNetwork.field.windowControlsPositionAria': 'Pozycja elementów sterujących oknem',
- 'settings.openchamber.desktopNetwork.option.windowControlsAuto': 'Automatycznie',
'settings.openchamber.desktopNetwork.option.windowControlsLeft': 'Lewo',
'settings.openchamber.desktopNetwork.option.windowControlsRight': 'Prawo',
'settings.openchamber.git.changesViewAria': 'Tryb widoku zmian Git',
diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts
index acf36555..04853cda 100644
--- a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts
+++ b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts
@@ -908,9 +908,8 @@ export const settingsDict = {
"settings.openchamber.sessionRetention.toast.failedDeleteCount": "Não foi possível excluir {count} sessão(es)",
"settings.openchamber.desktopNetwork.title": "Acesso de rede do desktop",
"settings.openchamber.desktopNetwork.field.windowControlsPosition": "Posição dos controles da janela",
- "settings.openchamber.desktopNetwork.field.windowControlsPositionDescription": "Escolha onde os botões de minimizar, maximizar e fechar aparecem. Automático segue o sistema operacional.",
+ "settings.openchamber.desktopNetwork.field.windowControlsPositionDescription": "Escolha onde os botões de minimizar, maximizar e fechar aparecem. O padrão é à direita.",
"settings.openchamber.desktopNetwork.field.windowControlsPositionAria": "Posição dos controles da janela",
- "settings.openchamber.desktopNetwork.option.windowControlsAuto": "Automático",
"settings.openchamber.desktopNetwork.option.windowControlsLeft": "Esquerda",
"settings.openchamber.desktopNetwork.option.windowControlsRight": "Direita",
"settings.openchamber.desktopNetwork.field.launchAtLoginAria": "Iniciar o OpenChamber ao fazer login",
diff --git a/packages/ui/src/lib/i18n/messages/uk.settings.ts b/packages/ui/src/lib/i18n/messages/uk.settings.ts
index 0671ae01..a714f93c 100644
--- a/packages/ui/src/lib/i18n/messages/uk.settings.ts
+++ b/packages/ui/src/lib/i18n/messages/uk.settings.ts
@@ -908,9 +908,8 @@ export const settingsDict = {
"settings.openchamber.sessionRetention.toast.failedDeleteCount": "Не вдалося видалити сесій: {count}",
"settings.openchamber.desktopNetwork.title": "Мережевий доступ десктопного застосунку",
"settings.openchamber.desktopNetwork.field.windowControlsPosition": "Позиція елементів керування вікном",
- "settings.openchamber.desktopNetwork.field.windowControlsPositionDescription": "Виберіть, де з’являються кнопки згортання, розгортання та закриття. Авто відповідає вашій операційній системі.",
+ "settings.openchamber.desktopNetwork.field.windowControlsPositionDescription": "Виберіть, де з’являються кнопки згортання, розгортання та закриття. За замовчуванням справа.",
"settings.openchamber.desktopNetwork.field.windowControlsPositionAria": "Позиція елементів керування вікном",
- "settings.openchamber.desktopNetwork.option.windowControlsAuto": "Авто",
"settings.openchamber.desktopNetwork.option.windowControlsLeft": "Зліва",
"settings.openchamber.desktopNetwork.option.windowControlsRight": "Справа",
"settings.openchamber.desktopNetwork.field.launchAtLoginAria": "Запускати OpenChamber під час входу в систему",
diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts
index d31fc788..563e8427 100644
--- a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts
+++ b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts
@@ -908,9 +908,8 @@ export const settingsDict = {
'settings.openchamber.sessionRetention.toast.failedDeleteCount': '删除 {count} 个会话失败',
'settings.openchamber.desktopNetwork.title': '桌面端网络访问',
'settings.openchamber.desktopNetwork.field.windowControlsPosition': '窗口控件位置',
- 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': '选择最小化、最大化和关闭按钮的显示位置。自动会按操作系统决定。',
+ 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': '选择最小化、最大化和关闭按钮的显示位置。默认在右侧。',
'settings.openchamber.desktopNetwork.field.windowControlsPositionAria': '窗口控件位置',
- 'settings.openchamber.desktopNetwork.option.windowControlsAuto': '自动',
'settings.openchamber.desktopNetwork.option.windowControlsLeft': '左侧',
'settings.openchamber.desktopNetwork.option.windowControlsRight': '右侧',
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': '登录时启动 OpenChamber',
diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts b/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts
index 9d85784f..90b47898 100644
--- a/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts
+++ b/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts
@@ -905,9 +905,8 @@
'settings.openchamber.sessionRetention.toast.failedDeleteCount': '刪除 {count} 個工作階段失敗',
'settings.openchamber.desktopNetwork.title': '桌面端網路存取',
'settings.openchamber.desktopNetwork.field.windowControlsPosition': '視窗控制項位置',
- 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': '選擇最小化、最大化和關閉按鈕的顯示位置。自動會依作業系統決定。',
+ 'settings.openchamber.desktopNetwork.field.windowControlsPositionDescription': '選擇最小化、最大化和關閉按鈕的顯示位置。預設在右側。',
'settings.openchamber.desktopNetwork.field.windowControlsPositionAria': '視窗控制項位置',
- 'settings.openchamber.desktopNetwork.option.windowControlsAuto': '自動',
'settings.openchamber.desktopNetwork.option.windowControlsLeft': '左側',
'settings.openchamber.desktopNetwork.option.windowControlsRight': '右側',
'settings.openchamber.desktopNetwork.field.allowLanAccessAria': '允許桌面 sidecar 區域網路存取',
diff --git a/packages/ui/src/lib/persistence.ts b/packages/ui/src/lib/persistence.ts
index 38bf9af9..7db5b302 100644
--- a/packages/ui/src/lib/persistence.ts
+++ b/packages/ui/src/lib/persistence.ts
@@ -738,10 +738,14 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
store.setWeekStartPreference(settings.weekStartPreference);
}
}
- if (typeof settings.desktopWindowControlsPosition === 'string'
- && (settings.desktopWindowControlsPosition === 'auto' || settings.desktopWindowControlsPosition === 'left' || settings.desktopWindowControlsPosition === 'right')) {
- if (settings.desktopWindowControlsPosition !== store.desktopWindowControlsPosition) {
- store.setDesktopWindowControlsPosition(settings.desktopWindowControlsPosition);
+ if (typeof settings.desktopWindowControlsPosition === 'string') {
+ const nextPosition = settings.desktopWindowControlsPosition === 'left'
+ ? 'left'
+ : (settings.desktopWindowControlsPosition === 'right' || settings.desktopWindowControlsPosition === 'auto')
+ ? 'right'
+ : null;
+ if (nextPosition && nextPosition !== store.desktopWindowControlsPosition) {
+ store.setDesktopWindowControlsPosition(nextPosition);
}
}
if (typeof settings.chatRenderMode === 'string'
@@ -1364,9 +1368,16 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
&& (candidate.weekStartPreference === 'auto' || candidate.weekStartPreference === 'sunday' || candidate.weekStartPreference === 'monday')) {
result.weekStartPreference = candidate.weekStartPreference;
}
- if (typeof candidate.desktopWindowControlsPosition === 'string'
- && (candidate.desktopWindowControlsPosition === 'auto' || candidate.desktopWindowControlsPosition === 'left' || candidate.desktopWindowControlsPosition === 'right')) {
- result.desktopWindowControlsPosition = candidate.desktopWindowControlsPosition;
+ if (typeof candidate.desktopWindowControlsPosition === 'string') {
+ if (candidate.desktopWindowControlsPosition === 'left') {
+ result.desktopWindowControlsPosition = 'left';
+ } else if (
+ candidate.desktopWindowControlsPosition === 'right'
+ || candidate.desktopWindowControlsPosition === 'auto'
+ ) {
+ // Legacy "auto" never read OS chrome config; treat as right.
+ result.desktopWindowControlsPosition = 'right';
+ }
}
if (typeof candidate.chatRenderMode === 'string'
&& (candidate.chatRenderMode === 'sorted' || candidate.chatRenderMode === 'live')) {
diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts
index 3c650a64..7cebc343 100644
--- a/packages/ui/src/stores/useUIStore.ts
+++ b/packages/ui/src/stores/useUIStore.ts
@@ -21,7 +21,7 @@ export type ActivityRenderMode = 'collapsed' | 'summary';
export type SessionRetentionAction = 'archive' | 'delete';
export type TimeFormatPreference = 'auto' | '12h' | '24h';
export type WeekStartPreference = 'auto' | 'sunday' | 'monday';
-export type DesktopWindowControlsPosition = 'auto' | 'left' | 'right';
+export type DesktopWindowControlsPosition = 'left' | 'right';
export type FileEditorKeymap = 'default' | 'vim';
function normalizeFileEditorKeymap(value: unknown): FileEditorKeymap {
@@ -971,7 +971,7 @@ export const useUIStore = create
()(
showExpandedEditTools: false,
timeFormatPreference: 'auto',
weekStartPreference: 'auto',
- desktopWindowControlsPosition: 'auto',
+ desktopWindowControlsPosition: 'right',
mermaidRenderingMode: 'svg',
userMessageRenderingMode: 'markdown',
collapsibleUserMessages: true,
@@ -2148,7 +2148,7 @@ export const useUIStore = create()(
set({ weekStartPreference: value });
},
setDesktopWindowControlsPosition: (value) => {
- set({ desktopWindowControlsPosition: value });
+ set({ desktopWindowControlsPosition: value === 'left' ? 'left' : 'right' });
},
setMermaidRenderingMode: (value) => {
set({ mermaidRenderingMode: value });
@@ -2234,13 +2234,20 @@ export const useUIStore = create()(
{
name: 'ui-store',
storage: createDeferredSafeJSONStorage(),
- version: 11,
+ version: 12,
migrate: (persistedState, version) => {
if (!persistedState || typeof persistedState !== 'object') {
return persistedState;
}
const state = persistedState as Record;
+ // v11 -> v12: drop legacy window-controls "auto" (always meant right).
+ if (version < 12) {
+ if (state.desktopWindowControlsPosition === 'auto' || state.desktopWindowControlsPosition == null) {
+ state.desktopWindowControlsPosition = 'right';
+ }
+ }
+
// v10 -> v11: move the previous terminal font default forward.
if (version < 11 && state.terminalFontSize === 13) {
state.terminalFontSize = 14;
diff --git a/packages/web/server/lib/opencode/settings-helpers.js b/packages/web/server/lib/opencode/settings-helpers.js
index c6882388..b95e1145 100644
--- a/packages/web/server/lib/opencode/settings-helpers.js
+++ b/packages/web/server/lib/opencode/settings-helpers.js
@@ -190,8 +190,11 @@ export const createSettingsHelpers = (dependencies) => {
}
if (typeof candidate.desktopWindowControlsPosition === 'string') {
const mode = candidate.desktopWindowControlsPosition.trim();
- if (mode === 'auto' || mode === 'left' || mode === 'right') {
- result.desktopWindowControlsPosition = mode;
+ // Legacy "auto" never read OS chrome config; persist as the right default.
+ if (mode === 'auto' || mode === 'right') {
+ result.desktopWindowControlsPosition = 'right';
+ } else if (mode === 'left') {
+ result.desktopWindowControlsPosition = 'left';
}
}
if (candidate.permissionAutoAccept && typeof candidate.permissionAutoAccept === 'object' && !Array.isArray(candidate.permissionAutoAccept)) {
diff --git a/packages/web/server/lib/opencode/settings-helpers.test.js b/packages/web/server/lib/opencode/settings-helpers.test.js
index accc81ee..20387b55 100644
--- a/packages/web/server/lib/opencode/settings-helpers.test.js
+++ b/packages/web/server/lib/opencode/settings-helpers.test.js
@@ -154,6 +154,21 @@ describe('settings helpers', () => {
});
});
+ it('normalizes desktopWindowControlsPosition and maps legacy auto to right', () => {
+ const helpers = createTestHelpers();
+
+ expect(helpers.sanitizeSettingsUpdate({ desktopWindowControlsPosition: 'left' })).toEqual({
+ desktopWindowControlsPosition: 'left',
+ });
+ expect(helpers.sanitizeSettingsUpdate({ desktopWindowControlsPosition: 'right' })).toEqual({
+ desktopWindowControlsPosition: 'right',
+ });
+ expect(helpers.sanitizeSettingsUpdate({ desktopWindowControlsPosition: 'auto' })).toEqual({
+ desktopWindowControlsPosition: 'right',
+ });
+ expect(helpers.sanitizeSettingsUpdate({ desktopWindowControlsPosition: 'center' })).toEqual({});
+ });
+
it('sanitizes the persisted permission auto-accept policy', () => {
const helpers = createTestHelpers();