From 4296efb64dcbda72165b9abc44d14b09612520c8 Mon Sep 17 00:00:00 2001 From: achcyano <78415212+achcyano@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:40:48 +0800 Subject: [PATCH] feat(electron): add Windows startup and system tray support (#2112) Add Windows launch-at-login with background startup support and extend the native tray integration to Windows. Add a Windows-only setting to minimize or close the main window to the system tray, persist it through desktop settings, and expose it in Settings search and all locale dictionaries. Keep tray state synchronized with live sessions on both macOS and Windows, while preserving the existing macOS behavior. --- packages/electron/main.mjs | 105 +++++++++++++++--- packages/electron/tray.mjs | 14 ++- .../openchamber/DesktopNetworkSettings.tsx | 82 ++++++++++++++ .../ui/src/components/views/SettingsView.tsx | 8 +- packages/ui/src/hooks/useTraySync.ts | 15 +-- packages/ui/src/lib/desktop.ts | 40 +++++++ .../ui/src/lib/i18n/messages/en.settings.ts | 7 +- .../ui/src/lib/i18n/messages/es.settings.ts | 7 +- .../ui/src/lib/i18n/messages/fr.settings.ts | 7 +- .../ui/src/lib/i18n/messages/ja.settings.ts | 7 +- .../ui/src/lib/i18n/messages/ko.settings.ts | 7 +- .../ui/src/lib/i18n/messages/pl.settings.ts | 7 +- .../src/lib/i18n/messages/pt-BR.settings.ts | 7 +- .../ui/src/lib/i18n/messages/uk.settings.ts | 7 +- .../src/lib/i18n/messages/zh-CN.settings.ts | 7 +- .../src/lib/i18n/messages/zh-TW.settings.ts | 7 +- packages/ui/src/lib/persistence.ts | 6 + packages/ui/src/lib/settings/search.ts | 10 ++ .../server/lib/opencode/settings-helpers.js | 3 + .../lib/opencode/settings-helpers.test.js | 11 ++ 20 files changed, 325 insertions(+), 39 deletions(-) diff --git a/packages/electron/main.mjs b/packages/electron/main.mjs index 816ef34a..70fa534b 100644 --- a/packages/electron/main.mjs +++ b/packages/electron/main.mjs @@ -29,10 +29,21 @@ const DEV_APP_USER_MODEL_ID = 'dev.openchamber.desktop.dev'; const APP_USER_MODEL_ID = app.isPackaged ? PACKAGED_APP_USER_MODEL_ID : DEV_APP_USER_MODEL_ID; const BACKGROUND_START_ARG = '--background'; +const getLoginItemOptions = () => { + if (process.platform === 'win32') { + return { + path: process.execPath, + args: [BACKGROUND_START_ARG], + name: APP_USER_MODEL_ID, + }; + } + return {}; +}; + const readLoginItemSettings = () => { - if (process.platform !== 'darwin') return null; + if (process.platform !== 'darwin' && process.platform !== 'win32') return null; try { - return app.getLoginItemSettings(); + return app.getLoginItemSettings(getLoginItemOptions()); } catch { return null; } @@ -225,6 +236,22 @@ const readDesktopKeepAwakeStatus = () => { return { supported: true, enabled, active }; }; +const readDesktopMinimizeToTrayStatus = () => { + const supported = process.platform === 'win32'; + return { + supported, + enabled: supported && readSettingsRoot().desktopMinimizeToTrayEnabled === true, + }; +}; + +const shouldHideMainWindowToTray = (browserWindow) => { + if (process.platform !== 'win32') return false; + if (!state.trayController) return false; + if (!browserWindow || browserWindow.isDestroyed()) return false; + if (browserWindow.__ocMiniChat === true) return false; + return readSettingsRoot().desktopMinimizeToTrayEnabled === true; +}; + const quitRisk = { hasActiveTunnel: false, hasRunningScheduledTasks: false, @@ -2278,7 +2305,20 @@ const createBrowserWindow = ({ label, restoreGeometry, url, runtimeConfig = {} } browserWindow.on('move', () => { debounceWindowStatePersist(browserWindow, false); }); + browserWindow.on('minimize', (event) => { + if (!shouldHideMainWindowToTray(browserWindow)) return; + debounceWindowStatePersist(browserWindow, true); + event.preventDefault(); + browserWindow.hide(); + }); browserWindow.on('close', (event) => { + if (!state.quitRequested && shouldHideMainWindowToTray(browserWindow)) { + debounceWindowStatePersist(browserWindow, true); + event.preventDefault(); + browserWindow.hide(); + return; + } + if (process.platform === 'darwin' && !state.quitRequested) { const remainingVisible = BrowserWindow.getAllWindows().filter( (window) => !window.isDestroyed() && window.isVisible(), @@ -3389,23 +3429,39 @@ const handleInvoke = async (browserWindow, command, args = {}) => { return APP_VERSION; case 'desktop_get_launch_at_login': { - if (process.platform !== 'darwin') return { supported: false, enabled: false }; - const settings = app.getLoginItemSettings(); + if (process.platform !== 'darwin' && process.platform !== 'win32') return { supported: false, enabled: false }; + const settings = app.getLoginItemSettings(getLoginItemOptions()); return { supported: true, enabled: settings.openAtLogin === true }; } case 'desktop_set_launch_at_login': { - if (process.platform !== 'darwin') return { supported: false, enabled: false }; + if (process.platform !== 'darwin' && process.platform !== 'win32') return { supported: false, enabled: false }; const enabled = args.enabled === true; - app.setLoginItemSettings({ + const settingsArgs = { openAtLogin: enabled, - openAsHidden: enabled, - args: enabled ? [BACKGROUND_START_ARG] : [], - }); - const settings = app.getLoginItemSettings(); + ...(process.platform === 'darwin' ? { openAsHidden: enabled } : {}), + ...(process.platform === 'win32' ? getLoginItemOptions() : { args: enabled ? [BACKGROUND_START_ARG] : [] }), + ...(process.platform === 'win32' ? { enabled } : {}), + }; + app.setLoginItemSettings(settingsArgs); + const settings = app.getLoginItemSettings(getLoginItemOptions()); return { supported: true, enabled: settings.openAtLogin === true }; } + case 'desktop_get_minimize_to_tray': { + return readDesktopMinimizeToTrayStatus(); + } + + case 'desktop_set_minimize_to_tray': { + if (process.platform !== 'win32') return { supported: false, enabled: false }; + const enabled = args.enabled === true; + await mutateSettingsRoot((root) => { + root.desktopMinimizeToTrayEnabled = enabled; + }); + setupTray(); + return readDesktopMinimizeToTrayStatus(); + } + case 'desktop_get_keep_awake': { return readDesktopKeepAwakeStatus(); } @@ -4525,8 +4581,8 @@ ipcMain.handle('openchamber:file:grant-existing', async (event, filePath) => { }; }); -// --- macOS menu bar (status bar) --------------------------------------------- -// Tray lives only on macOS; the renderer streams a compact state snapshot via +// --- Native tray / menu bar --------------------------------------------------- +// Tray lives on macOS and Windows; the renderer streams a compact state snapshot via // the `desktop_tray_update` IPC command (see the command switch). Tray clicks // flow back through dispatchTrayAction → renderer (focus/respond) or native // handlers (show window / quit). @@ -4558,6 +4614,21 @@ const resolveTraySurface = () => { const trayIconAssets = () => { const dir = path.join(resourceRoot(), 'icons', 'tray'); const statusDir = path.join(dir, 'status'); + if (process.platform === 'win32') { + const iconPath = getWindowIconPath() || path.join(resourceRoot(), 'icons', 'icon.ico'); + return { + idleIconPath: iconPath, + unseenIconPath: iconPath, + breathIconPaths: [iconPath], + statusIconPaths: { + busy: path.join(statusDir, 'busy.png'), + retry: path.join(statusDir, 'retry.png'), + error: path.join(statusDir, 'error.png'), + unseen: path.join(statusDir, 'unseen.png'), + blank: path.join(statusDir, 'blank.png'), + }, + }; + } return { idleIconPath: path.join(dir, 'trayTemplate-idle.png'), unseenIconPath: path.join(dir, 'trayTemplate-unseen.png'), @@ -4576,7 +4647,7 @@ const trayIconAssets = () => { }; const setupTray = () => { - if (process.platform !== 'darwin' || state.trayController) return; + if (!['darwin', 'win32'].includes(process.platform) || state.trayController) return; const assets = trayIconAssets(); if (!fs.existsSync(assets.idleIconPath)) { log.warn('[electron] tray icon missing, skipping tray setup', { iconPath: assets.idleIconPath }); @@ -4778,17 +4849,17 @@ app.whenReady().then(async () => { if (process.platform === 'darwin') { Menu.setApplicationMenu(buildMacMenu()); - setupTray(); } else { Menu.setApplicationMenu(buildAutoHiddenMenu()); } + setupTray(); - if (process.platform === 'darwin' && app.isPackaged) { + if ((process.platform === 'darwin' || process.platform === 'win32') && app.isPackaged) { const openAtLogin = loginItemSettings?.openAtLogin === true; app.setLoginItemSettings({ openAtLogin, - openAsHidden: openAtLogin, - args: openAtLogin ? [BACKGROUND_START_ARG] : [], + ...(process.platform === 'darwin' ? { openAsHidden: openAtLogin, args: openAtLogin ? [BACKGROUND_START_ARG] : [] } : {}), + ...(process.platform === 'win32' ? { ...getLoginItemOptions(), enabled: openAtLogin } : {}), }); } diff --git a/packages/electron/tray.mjs b/packages/electron/tray.mjs index 721c891c..51cc251b 100644 --- a/packages/electron/tray.mjs +++ b/packages/electron/tray.mjs @@ -1,4 +1,4 @@ -// macOS menu bar (status bar) controller. +// Native tray/menu bar controller. // // Surfaces a glanceable, always-visible view of OpenChamber's live state: // 1. an aggregate activity indicator (idle / busy / error+retry) in the icon @@ -17,6 +17,8 @@ import { Tray, Menu, nativeImage } from 'electron'; +const isMac = process.platform === 'darwin'; + const MAX_SESSIONS = 8; const MAX_APPROVALS = 10; @@ -87,7 +89,7 @@ const ANIM_INTERVAL_MS = 75; const toTemplateImage = (p) => { const image = nativeImage.createFromPath(p); - image.setTemplateImage(true); + if (isMac) image.setTemplateImage(true); return image; }; @@ -99,6 +101,7 @@ export const createTrayController = ({ idleIconPath, unseenIconPath, breathIconP let lastTitle = null; // macOS auto-picks the @2x file next to each path and tints the alpha. + // Windows uses the regular app icon and ignores template tinting. const idleFrame = toTemplateImage(idleIconPath); const unseenFrame = toTemplateImage(unseenIconPath); const breathFrames = breathIconPaths.map(toTemplateImage); @@ -122,6 +125,7 @@ export const createTrayController = ({ idleIconPath, unseenIconPath, breathIconP const startAnim = () => { if (animTimer || !tray || tray.isDestroyed?.()) return; + if (breathFrames.length < 2) return; animIndex = 0; animDir = 1; animTimer = setInterval(() => { @@ -139,7 +143,8 @@ export const createTrayController = ({ idleIconPath, unseenIconPath, breathIconP iconState = nextState; if (!tray || tray.isDestroyed?.()) return; if (nextState === 'busy') { - startAnim(); + if (breathFrames.length > 1) startAnim(); + else tray.setImage(breathFrames[0] || idleFrame); } else if (nextState === 'unseen') { stopAnim(); tray.setImage(unseenFrame); @@ -153,6 +158,9 @@ export const createTrayController = ({ idleIconPath, unseenIconPath, breathIconP if (tray && !tray.isDestroyed?.()) return tray; tray = new Tray(idleFrame); tray.setIgnoreDoubleClickEvents(true); + if (!isMac) { + tray.on('click', () => onAction({ type: 'show-main-window' })); + } return tray; }; diff --git a/packages/ui/src/components/sections/openchamber/DesktopNetworkSettings.tsx b/packages/ui/src/components/sections/openchamber/DesktopNetworkSettings.tsx index 661d492f..df480fcc 100644 --- a/packages/ui/src/components/sections/openchamber/DesktopNetworkSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/DesktopNetworkSettings.tsx @@ -7,11 +7,13 @@ import { getDesktopLanAddress, getDesktopKeepAwake, getDesktopLaunchAtLogin, + getDesktopMinimizeToTray, isDesktopLocalOriginActive, isDesktopShell, restartDesktopApp, setDesktopKeepAwake, setDesktopLaunchAtLogin, + setDesktopMinimizeToTray, } from '@/lib/desktop'; import { useI18n } from '@/lib/i18n'; import { runtimeFetch } from '@/lib/runtime-fetch'; @@ -31,6 +33,9 @@ export const DesktopNetworkSettings: React.FC = () => { const [launchAtLoginSupported, setLaunchAtLoginSupported] = React.useState(false); const [launchAtLoginEnabled, setLaunchAtLoginEnabled] = React.useState(false); const [isSavingLaunchAtLogin, setIsSavingLaunchAtLogin] = React.useState(false); + const [minimizeToTraySupported, setMinimizeToTraySupported] = React.useState(false); + const [minimizeToTrayEnabled, setMinimizeToTrayEnabled] = React.useState(false); + const [isSavingMinimizeToTray, setIsSavingMinimizeToTray] = React.useState(false); const [keepAwakeSupported, setKeepAwakeSupported] = React.useState(false); const [keepAwakeEnabled, setKeepAwakeEnabled] = React.useState(false); const [isSavingKeepAwake, setIsSavingKeepAwake] = React.useState(false); @@ -112,6 +117,27 @@ export const DesktopNetworkSettings: React.FC = () => { }; }, [isLocalDesktop]); + React.useEffect(() => { + if (!isLocalDesktop) { + setMinimizeToTraySupported(false); + return; + } + + let cancelled = false; + void (async () => { + const status = await getDesktopMinimizeToTray(); + if (cancelled) { + return; + } + setMinimizeToTraySupported(status?.supported === true); + setMinimizeToTrayEnabled(status?.enabled === true); + })(); + + return () => { + cancelled = true; + }; + }, [isLocalDesktop]); + React.useEffect(() => { if (!isLocalDesktop) { setKeepAwakeSupported(false); @@ -209,6 +235,33 @@ export const DesktopNetworkSettings: React.FC = () => { } }, [isSavingLaunchAtLogin, launchAtLoginEnabled, launchAtLoginSupported, t]); + const handleMinimizeToTrayToggle = React.useCallback(async () => { + if (!minimizeToTraySupported || isSavingMinimizeToTray) { + return; + } + + const nextValue = !minimizeToTrayEnabled; + setMinimizeToTrayEnabled(nextValue); + setIsSavingMinimizeToTray(true); + setError(null); + + try { + const status = await setDesktopMinimizeToTray(nextValue); + if (!status) { + throw new Error(t('settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed')); + } + if (!status.supported) { + throw new Error(t('settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported')); + } + setMinimizeToTrayEnabled(status.enabled); + } catch (cause) { + setMinimizeToTrayEnabled(!nextValue); + setError(cause instanceof Error ? cause.message : t('settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed')); + } finally { + setIsSavingMinimizeToTray(false); + } + }, [isSavingMinimizeToTray, minimizeToTrayEnabled, minimizeToTraySupported, t]); + const handleKeepAwakeToggle = React.useCallback(async () => { if (!keepAwakeSupported || isSavingKeepAwake) { return; @@ -311,6 +364,35 @@ export const DesktopNetworkSettings: React.FC = () => { ) : null} + {minimizeToTraySupported ? ( +
{ + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + handleMinimizeToTrayToggle(); + } + }} + > + +
+
{t('settings.openchamber.desktopNetwork.field.minimizeToTray')}
+
+ {t('settings.openchamber.desktopNetwork.field.minimizeToTrayDescription')} +
+
+
+ ) : null} + {keepAwakeSupported ? (
= ({ onClose, forceMobile return isDesktopShell() && typeof window !== 'undefined' && (window as unknown as { __OPENCHAMBER_PLATFORM__?: string }).__OPENCHAMBER_PLATFORM__ === 'darwin'; }, []); + const isWindows = React.useMemo(() => { + return isDesktopShell() && typeof window !== 'undefined' + && (window as unknown as { __OPENCHAMBER_PLATFORM__?: string }).__OPENCHAMBER_PLATFORM__ === 'win32'; + }, []); // keep platform check available for future window chrome tweaks @@ -536,12 +540,12 @@ export const SettingsView: React.FC = ({ onClose, forceMobile const settingsSearchResults = React.useMemo(() => { return buildSettingsSearchResults({ query: settingsSearchQuery, - runtimeCtx: { ...runtimeCtx, isDesktopLocalOrigin, isMac }, + runtimeCtx: { ...runtimeCtx, isDesktopLocalOrigin, isMac, isWindows }, visiblePageSlugs, t, getPageTitle, }); - }, [getPageTitle, isDesktopLocalOrigin, isMac, runtimeCtx, settingsSearchQuery, t, visiblePageSlugs]); + }, [getPageTitle, isDesktopLocalOrigin, isMac, isWindows, runtimeCtx, settingsSearchQuery, t, visiblePageSlugs]); const prepareSettingsSearchTarget = React.useCallback((result: SettingsSearchResult): string => { if (result.id.startsWith('agents.')) { diff --git a/packages/ui/src/hooks/useTraySync.ts b/packages/ui/src/hooks/useTraySync.ts index 0a10a32e..25864f76 100644 --- a/packages/ui/src/hooks/useTraySync.ts +++ b/packages/ui/src/hooks/useTraySync.ts @@ -27,12 +27,12 @@ import { toast } from '@/components/ui'; import type { PermissionRequest } from '@/types/permission'; import type { QuestionRequest } from '@/types/question'; -// macOS menu bar bridge. The Electron main process owns the Tray UI; this hook +// Native tray/menu bar bridge. The Electron main process owns the Tray UI; this hook // streams a compact snapshot of live session/approval state to it via the // `desktop_tray_update` IPC command, and routes tray clicks back into the app. // -// Only meaningful on the macOS desktop shell — main.mjs no-ops the command on -// other platforms, but we still gate here to avoid pointless work. +// Only meaningful on desktop platforms with a native tray/menu bar — main.mjs +// no-ops the command elsewhere, but we still gate here to avoid pointless work. const TRAY_ACTION_EVENT = 'openchamber:tray-action'; // Event-driven updates do the real work; this is just a slow safety net. @@ -99,9 +99,10 @@ type DesktopBridgeGlobal = { ) => Promise<() => void>; }; -const isMac = (): boolean => { +const isTrayPlatform = (): boolean => { if (typeof window === 'undefined') return false; - return (window as unknown as { __OPENCHAMBER_PLATFORM__?: string }).__OPENCHAMBER_PLATFORM__ === 'darwin'; + const platform = (window as unknown as { __OPENCHAMBER_PLATFORM__?: string }).__OPENCHAMBER_PLATFORM__; + return platform === 'darwin' || platform === 'win32'; }; const permissionLabel = (request: PermissionRequest): string => { @@ -417,7 +418,7 @@ const buildSnapshot = (instanceName: string): TraySnapshot => { export const useTraySync = (): void => { React.useEffect(() => { - if (!isMac() || !canUseElectronDesktopIPC()) return; + if (!isTrayPlatform() || !canUseElectronDesktopIPC()) return; let disposed = false; let lastSerialized = ''; @@ -577,7 +578,7 @@ export const useTraySync = (): void => { }, []); React.useEffect(() => { - if (!isMac() || typeof window === 'undefined') return; + if (!isTrayPlatform() || typeof window === 'undefined') return; const bridge = (window as unknown as { __OPENCHAMBER_DESKTOP__?: DesktopBridgeGlobal }).__OPENCHAMBER_DESKTOP__; const listen = bridge?.listen; if (typeof listen !== 'function') return; diff --git a/packages/ui/src/lib/desktop.ts b/packages/ui/src/lib/desktop.ts index 593d27b9..2d483992 100644 --- a/packages/ui/src/lib/desktop.ts +++ b/packages/ui/src/lib/desktop.ts @@ -54,6 +54,7 @@ export type DesktopSettings = { opencodeBinary?: string; desktopLanAccessEnabled?: boolean; desktopKeepAwakeEnabled?: boolean; + desktopMinimizeToTrayEnabled?: boolean; desktopUiPassword?: string; projects?: ProjectEntry[]; activeProjectId?: string; @@ -247,6 +248,11 @@ type KeepAwakeStatus = { active: boolean; }; +type MinimizeToTrayStatus = { + supported: boolean; + enabled: boolean; +}; + export const getDesktopLaunchAtLogin = async (): Promise => { if (!canUseElectronDesktopIPC() || !isDesktopLocalOriginActive()) { return null; @@ -281,6 +287,40 @@ export const setDesktopLaunchAtLogin = async (enabled: boolean): Promise => { + if (!canUseElectronDesktopIPC() || !isDesktopLocalOriginActive()) { + return null; + } + + try { + const result = await invokeDesktop('desktop_get_minimize_to_tray'); + if (!result || typeof result.supported !== 'boolean' || typeof result.enabled !== 'boolean') { + return null; + } + return result; + } catch (error) { + console.warn('Failed to get minimize to tray status', error); + return null; + } +}; + +export const setDesktopMinimizeToTray = async (enabled: boolean): Promise => { + if (!canUseElectronDesktopIPC() || !isDesktopLocalOriginActive()) { + return null; + } + + try { + const result = await invokeDesktop('desktop_set_minimize_to_tray', { enabled }); + if (!result || typeof result.supported !== 'boolean' || typeof result.enabled !== 'boolean') { + return null; + } + return result; + } catch (error) { + console.warn('Failed to set minimize to tray status', error); + return null; + } +}; + export const getDesktopKeepAwake = async (): Promise => { if (!canUseElectronDesktopIPC() || !isDesktopLocalOriginActive()) { return null; diff --git a/packages/ui/src/lib/i18n/messages/en.settings.ts b/packages/ui/src/lib/i18n/messages/en.settings.ts index 6e33123a..8733b129 100644 --- a/packages/ui/src/lib/i18n/messages/en.settings.ts +++ b/packages/ui/src/lib/i18n/messages/en.settings.ts @@ -877,7 +877,10 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.title': 'Desktop Network Access', 'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'Start OpenChamber at login', 'settings.openchamber.desktopNetwork.field.launchAtLogin': 'Start OpenChamber when you log in', - 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Starts the app in the background without opening a window. Click the Dock icon to open it.', + 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Starts the app in the background without opening a window. Use the desktop status icon to open it.', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': 'Minimize and close OpenChamber to the system tray', + 'settings.openchamber.desktopNetwork.field.minimizeToTray': 'Minimize and close to the system tray', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': 'Keeps OpenChamber running in the Windows system tray when the main window is minimized or closed.', 'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'Keep computer awake while OpenChamber is running', 'settings.openchamber.desktopNetwork.field.keepAwake': 'Keep computer awake while OpenChamber is running', 'settings.openchamber.desktopNetwork.field.keepAwakeDescription': 'Prevents system sleep so phones can keep reaching this app. The screen can still turn off.', @@ -897,6 +900,8 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.error.savedRestartFailed': 'Saved, but failed to restart app', 'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': 'Launch at login is not supported on this system', 'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': 'Failed to update launch at login setting', + 'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': 'System tray background mode is not supported on this system', + 'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': 'Failed to update system tray setting', 'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': 'Preventing sleep is not supported on this system', 'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': 'Failed to update keep awake setting', 'settings.openchamber.opencodeCli.title': 'OpenCode CLI', diff --git a/packages/ui/src/lib/i18n/messages/es.settings.ts b/packages/ui/src/lib/i18n/messages/es.settings.ts index b05ce65e..75a93be9 100644 --- a/packages/ui/src/lib/i18n/messages/es.settings.ts +++ b/packages/ui/src/lib/i18n/messages/es.settings.ts @@ -844,7 +844,10 @@ export const settingsDict = { "settings.openchamber.desktopNetwork.title": "Acceso de red de escritorio", "settings.openchamber.desktopNetwork.field.launchAtLoginAria": "Iniciar OpenChamber al iniciar sesión", "settings.openchamber.desktopNetwork.field.launchAtLogin": "Iniciar OpenChamber al iniciar sesión", - "settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Inicia la app en segundo plano sin abrir una ventana. Haz clic en el icono del Dock para abrirla.", + "settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Inicia la app en segundo plano sin abrir una ventana. Usa el icono de estado del escritorio para abrirla.", + "settings.openchamber.desktopNetwork.field.minimizeToTrayAria": "Minimizar y cerrar OpenChamber a la bandeja del sistema", + "settings.openchamber.desktopNetwork.field.minimizeToTray": "Minimizar y cerrar a la bandeja del sistema", + "settings.openchamber.desktopNetwork.field.minimizeToTrayDescription": "Mantiene OpenChamber en ejecución en la bandeja del sistema de Windows cuando la ventana principal se minimiza o se cierra.", "settings.openchamber.desktopNetwork.field.keepAwakeAria": "Mantener el ordenador activo mientras OpenChamber está en ejecución", "settings.openchamber.desktopNetwork.field.keepAwake": "Mantener el ordenador activo mientras OpenChamber está en ejecución", "settings.openchamber.desktopNetwork.field.keepAwakeDescription": "Para que los teléfonos puedan seguir abriendo esta app. La pantalla aún puede apagarse.", @@ -864,6 +867,8 @@ export const settingsDict = { "settings.openchamber.desktopNetwork.error.savedRestartFailed": "Guardado, pero no se pudo reiniciar la aplicación", "settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported": "El inicio al iniciar sesión no es compatible con este sistema", "settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed": "No se pudo actualizar la opción de inicio al iniciar sesión", + "settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported": "El modo en segundo plano de la bandeja del sistema no es compatible con este sistema", + "settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed": "No se pudo actualizar la opción de bandeja del sistema", "settings.openchamber.desktopNetwork.error.keepAwakeUnsupported": "Evitar la suspensión no es compatible con este sistema", "settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed": "No se pudo actualizar la opción de mantener activo", "settings.openchamber.opencodeCli.title": "CLI de OpenCode", diff --git a/packages/ui/src/lib/i18n/messages/fr.settings.ts b/packages/ui/src/lib/i18n/messages/fr.settings.ts index 56ca828f..b3603c35 100644 --- a/packages/ui/src/lib/i18n/messages/fr.settings.ts +++ b/packages/ui/src/lib/i18n/messages/fr.settings.ts @@ -772,7 +772,10 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.title': 'Accès au réseau de bureau', 'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'Démarrez OpenChamber lors de la connexion', 'settings.openchamber.desktopNetwork.field.launchAtLogin': 'Démarrez OpenChamber lorsque vous vous connectez', - 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Démarre l\'application en arrière-plan sans ouvrir de fenêtre. Cliquez sur l\'icône Dock pour l\'ouvrir.', + 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Démarre l\'application en arrière-plan sans ouvrir de fenêtre. Utilisez l\'icône d\'état du bureau pour l\'ouvrir.', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': 'Réduire et fermer OpenChamber dans la zone de notification', + 'settings.openchamber.desktopNetwork.field.minimizeToTray': 'Réduire et fermer dans la zone de notification', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': 'Garde OpenChamber actif dans la zone de notification Windows lorsque la fenêtre principale est réduite ou fermée.', 'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'Garder l\'ordinateur éveillé pendant qu\'OpenChamber fonctionne', 'settings.openchamber.desktopNetwork.field.keepAwake': 'Garder l\'ordinateur éveillé pendant qu\'OpenChamber fonctionne', 'settings.openchamber.desktopNetwork.field.keepAwakeDescription': 'Pour que les téléphones puissent continuer à ouvrir cette application. L\'écran peut toujours s\'éteindre.', @@ -792,6 +795,8 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.error.savedRestartFailed': 'Enregistré, mais échec du redémarrage de l\'application', 'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': 'Le lancement à la connexion n\'est pas pris en charge sur ce système', 'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': 'Échec de la mise à jour du paramètre de lancement lors de la connexion', + 'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': 'Le mode d\'arrière-plan dans la zone de notification n\'est pas pris en charge sur ce système', + 'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': 'Échec de la mise à jour du paramètre de zone de notification', 'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': 'La prévention de la veille n\'est pas prise en charge sur ce système', 'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': 'Échec de la mise à jour du paramètre de maintien en éveil', 'settings.openchamber.opencodeCli.title': 'OpenCode CLI', diff --git a/packages/ui/src/lib/i18n/messages/ja.settings.ts b/packages/ui/src/lib/i18n/messages/ja.settings.ts index 701768f5..40cd2e0e 100644 --- a/packages/ui/src/lib/i18n/messages/ja.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ja.settings.ts @@ -877,7 +877,10 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.title': 'Desktop ネットワークアクセス', 'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'ログイン時に OpenChamber を起動', 'settings.openchamber.desktopNetwork.field.launchAtLogin': 'ログイン時に OpenChamber を起動', - 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'ウィンドウを開かずにバックグラウンドでアプリを起動します。Dock アイコンをクリックして開きます。', + 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'ウィンドウを開かずにバックグラウンドでアプリを起動します。デスクトップのステータスアイコンから開けます。', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': 'OpenChamber をシステムトレイへ最小化または閉じる', + 'settings.openchamber.desktopNetwork.field.minimizeToTray': 'システムトレイへ最小化または閉じる', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': 'メインウィンドウを最小化または閉じたときも、OpenChamber を Windows のシステムトレイで実行し続けます。', 'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'OpenChamber の実行中はコンピューターのスリープを防止', 'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber の実行中はコンピューターのスリープを防止', 'settings.openchamber.desktopNetwork.field.keepAwakeDescription': 'スマートフォンがこのアプリを開き続けられるようにします。ディスプレイはオフにできます。', @@ -897,6 +900,8 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.error.savedRestartFailed': '保存しましたが、アプリの再起動に失敗しました', 'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': 'このシステムではログイン時の起動はサポートされていません', 'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': 'ログイン時起動設定の更新に失敗しました', + 'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': 'このシステムではシステムトレイのバックグラウンドモードはサポートされていません', + 'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': 'システムトレイ設定の更新に失敗しました', 'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': 'このシステムではスリープ防止はサポートされていません', 'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': 'スリープ防止設定の更新に失敗しました', 'settings.openchamber.opencodeCli.title': 'OpenCode CLI', diff --git a/packages/ui/src/lib/i18n/messages/ko.settings.ts b/packages/ui/src/lib/i18n/messages/ko.settings.ts index f2652802..9aceeaf7 100644 --- a/packages/ui/src/lib/i18n/messages/ko.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ko.settings.ts @@ -844,7 +844,10 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.title': 'Desktop 네트워크 접속', 'settings.openchamber.desktopNetwork.field.launchAtLoginAria': '로그인 시 OpenChamber 시작', 'settings.openchamber.desktopNetwork.field.launchAtLogin': '로그인 시 OpenChamber 시작', - 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '창을 열지 않고 백그라운드에서 앱을 시작합니다. Dock 아이콘을 클릭하면 열립니다.', + 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '창을 열지 않고 백그라운드에서 앱을 시작합니다. 데스크톱 상태 아이콘으로 열 수 있습니다.', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': 'OpenChamber를 시스템 트레이로 최소화하거나 닫기', + 'settings.openchamber.desktopNetwork.field.minimizeToTray': '시스템 트레이로 최소화하고 닫기', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': '메인 창을 최소화하거나 닫아도 OpenChamber가 Windows 시스템 트레이에서 계속 실행됩니다.', 'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'OpenChamber가 실행되는 동안 컴퓨터 절전 방지', 'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber가 실행되는 동안 컴퓨터 절전 방지', 'settings.openchamber.desktopNetwork.field.keepAwakeDescription': '휴대폰이 이 앱을 계속 열 수 있도록 합니다. 디스플레이는 꺼질 수 있습니다.', @@ -864,6 +867,8 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.error.savedRestartFailed': '저장했지만 앱을 다시 시작하지 못했습니다', 'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': '이 시스템에서는 로그인 시 시작을 지원하지 않습니다', 'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': '로그인 시 시작 설정을 업데이트하지 못했습니다', + 'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': '이 시스템에서는 시스템 트레이 백그라운드 모드를 지원하지 않습니다', + 'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': '시스템 트레이 설정을 업데이트하지 못했습니다', 'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': '이 시스템에서는 절전 방지를 지원하지 않습니다', 'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': '절전 방지 설정을 업데이트하지 못했습니다', 'settings.openchamber.opencodeCli.title': 'OpenCode CLI', diff --git a/packages/ui/src/lib/i18n/messages/pl.settings.ts b/packages/ui/src/lib/i18n/messages/pl.settings.ts index 0e566a08..acdecd69 100644 --- a/packages/ui/src/lib/i18n/messages/pl.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pl.settings.ts @@ -692,6 +692,8 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.actions.saveAndRestart': 'Zapisz + Zrestartuj', 'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': 'Uruchamianie przy logowaniu nie jest obsługiwane w tym systemie', 'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': 'Nie udało się zaktualizować ustawienia uruchamiania przy logowaniu', + 'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': 'Tryb działania w tle w zasobniku systemowym nie jest obsługiwany w tym systemie', + 'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': 'Nie udało się zaktualizować ustawienia zasobnika systemowego', 'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': 'Zapobieganie uśpieniu nie jest obsługiwane w tym systemie', 'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': 'Nie udało się zaktualizować ustawienia utrzymywania aktywności', 'settings.openchamber.desktopNetwork.error.loadFailed': 'Nie udało się załadować ustawień pulpitu', @@ -702,7 +704,10 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.field.allowLanAccessDescription': 'Restartuje aplikację, aby telefony, tablety i inne komputery w Twojej sieci Wi-Fi mogły ją otworzyć.', 'settings.openchamber.desktopNetwork.field.launchAtLogin': 'Uruchamiaj OpenChamber przy logowaniu', 'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'Uruchamiaj OpenChamber przy logowaniu', - 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Uruchamia aplikację w tle bez otwierania okna. Kliknij ikonę w Docku, aby ją otworzyć.', + 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Uruchamia aplikację w tle bez otwierania okna. Otworzysz ją z ikony stanu na pulpicie.', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': 'Minimalizuj i zamykaj OpenChamber do zasobnika systemowego', + 'settings.openchamber.desktopNetwork.field.minimizeToTray': 'Minimalizuj i zamykaj do zasobnika systemowego', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': 'Utrzymuje OpenChamber w zasobniku systemowym Windows, gdy główne okno jest zminimalizowane lub zamknięte.', 'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'Utrzymuj komputer aktywny, gdy OpenChamber jest uruchomiony', 'settings.openchamber.desktopNetwork.field.keepAwake': 'Utrzymuj komputer aktywny, gdy OpenChamber jest uruchomiony', 'settings.openchamber.desktopNetwork.field.keepAwakeDescription': 'Aby telefony nadal mogły otwierać tę aplikację. Ekran nadal może się wyłączyć.', 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 db017d39..ea273353 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts @@ -844,7 +844,10 @@ export const settingsDict = { "settings.openchamber.desktopNetwork.title": "Acesso de rede do desktop", "settings.openchamber.desktopNetwork.field.launchAtLoginAria": "Iniciar o OpenChamber ao fazer login", "settings.openchamber.desktopNetwork.field.launchAtLogin": "Iniciar o OpenChamber ao fazer login", - "settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Inicia o app em segundo plano sem abrir uma janela. Clique no ícone do Dock para abrir.", + "settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Inicia o app em segundo plano sem abrir uma janela. Use o ícone de status da área de trabalho para abrir.", + "settings.openchamber.desktopNetwork.field.minimizeToTrayAria": "Minimizar e fechar o OpenChamber na bandeja do sistema", + "settings.openchamber.desktopNetwork.field.minimizeToTray": "Minimizar e fechar na bandeja do sistema", + "settings.openchamber.desktopNetwork.field.minimizeToTrayDescription": "Mantém o OpenChamber em execução na bandeja do sistema do Windows quando a janela principal é minimizada ou fechada.", "settings.openchamber.desktopNetwork.field.keepAwakeAria": "Manter o computador ativo enquanto o OpenChamber estiver em execução", "settings.openchamber.desktopNetwork.field.keepAwake": "Manter o computador ativo enquanto o OpenChamber estiver em execução", "settings.openchamber.desktopNetwork.field.keepAwakeDescription": "Para que telefones continuem abrindo este app. A tela ainda pode desligar.", @@ -864,6 +867,8 @@ export const settingsDict = { "settings.openchamber.desktopNetwork.error.savedRestartFailed": "Salvo, mas não foi possível reiniciar o aplicativo", "settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported": "Iniciar ao fazer login não é compatível com este sistema", "settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed": "Não foi possível atualizar a opção de iniciar ao fazer login", + "settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported": "O modo em segundo plano na bandeja do sistema não é compatível com este sistema", + "settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed": "Não foi possível atualizar a opção da bandeja do sistema", "settings.openchamber.desktopNetwork.error.keepAwakeUnsupported": "Impedir suspensão não é compatível com este sistema", "settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed": "Não foi possível atualizar a opção de manter ativo", "settings.openchamber.opencodeCli.title": "CLI do OpenCode", diff --git a/packages/ui/src/lib/i18n/messages/uk.settings.ts b/packages/ui/src/lib/i18n/messages/uk.settings.ts index 5b01f330..8bca1b7d 100644 --- a/packages/ui/src/lib/i18n/messages/uk.settings.ts +++ b/packages/ui/src/lib/i18n/messages/uk.settings.ts @@ -844,7 +844,10 @@ export const settingsDict = { "settings.openchamber.desktopNetwork.title": "Мережевий доступ десктопного застосунку", "settings.openchamber.desktopNetwork.field.launchAtLoginAria": "Запускати OpenChamber під час входу в систему", "settings.openchamber.desktopNetwork.field.launchAtLogin": "Запускати OpenChamber під час входу в систему", - "settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Запускає застосунок у фоні без відкриття вікна. Щоб відкрити його, натисніть іконку в Dock.", + "settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Запускає застосунок у фоні без відкриття вікна. Відкрийте його через піктограму стану на робочому столі.", + "settings.openchamber.desktopNetwork.field.minimizeToTrayAria": "Згортати й закривати OpenChamber у системний трей", + "settings.openchamber.desktopNetwork.field.minimizeToTray": "Згортати й закривати в системний трей", + "settings.openchamber.desktopNetwork.field.minimizeToTrayDescription": "Залишає OpenChamber запущеним у системному треї Windows, коли головне вікно згорнуто або закрито.", "settings.openchamber.desktopNetwork.field.keepAwakeAria": "Не давати комп’ютеру засинати, поки OpenChamber запущено", "settings.openchamber.desktopNetwork.field.keepAwake": "Не давати комп’ютеру засинати, поки OpenChamber запущено", "settings.openchamber.desktopNetwork.field.keepAwakeDescription": "Щоб телефон і далі міг відкривати застосунок. Екран усе одно може вимикатися.", @@ -864,6 +867,8 @@ export const settingsDict = { "settings.openchamber.desktopNetwork.error.savedRestartFailed": "Збережено, але не вдалося перезапустити застосунок", "settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported": "Запуск під час входу в систему не підтримується на цій системі", "settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed": "Не вдалося оновити налаштування запуску під час входу в систему", + "settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported": "Фоновий режим у системному треї не підтримується на цій системі", + "settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed": "Не вдалося оновити налаштування системного трея", "settings.openchamber.desktopNetwork.error.keepAwakeUnsupported": "Запобігання сну не підтримується на цій системі", "settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed": "Не вдалося оновити налаштування утримання комп’ютера активним", "settings.openchamber.opencodeCli.title": "OpenCode CLI", 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 86945ca1..28ba6c53 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts @@ -844,7 +844,10 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.title': '桌面端网络访问', 'settings.openchamber.desktopNetwork.field.launchAtLoginAria': '登录时启动 OpenChamber', 'settings.openchamber.desktopNetwork.field.launchAtLogin': '登录时启动 OpenChamber', - 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '在后台启动应用且不打开窗口。点击 Dock 图标即可打开。', + 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '在后台启动应用且不打开窗口。可通过桌面状态图标打开。', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': '将 OpenChamber 最小化和关闭到系统托盘', + 'settings.openchamber.desktopNetwork.field.minimizeToTray': '最小化和关闭到系统托盘', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': '主窗口最小化或关闭时,让 OpenChamber 继续在 Windows 系统托盘中运行。', 'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'OpenChamber 运行时保持电脑唤醒', 'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber 运行时保持电脑唤醒', 'settings.openchamber.desktopNetwork.field.keepAwakeDescription': '让手机可以继续打开此应用。屏幕仍可关闭。', @@ -864,6 +867,8 @@ export const settingsDict = { 'settings.openchamber.desktopNetwork.error.savedRestartFailed': '已保存,但重启应用失败', 'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': '此系统不支持登录时启动', 'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': '更新登录时启动设置失败', + 'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': '此系统不支持系统托盘后台模式', + 'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': '更新系统托盘设置失败', 'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': '此系统不支持防止睡眠', 'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': '更新保持唤醒设置失败', 'settings.openchamber.opencodeCli.title': 'OpenCode CLI', 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 b58047db..999e545d 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts @@ -1817,11 +1817,16 @@ 'settings.promptTemplates.page.toast.saveUnexpectedError': '儲存時發生意外錯誤', 'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': '無法更新登入時自動啟動設定', 'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': '此系統不支援登入時自動啟動', + 'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': '此系統不支援系統匣背景模式', + 'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': '無法更新系統匣設定', 'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': '此系統不支援防止睡眠', 'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': '無法更新保持喚醒設定', 'settings.openchamber.desktopNetwork.field.launchAtLogin': '登入時自動啟動 OpenChamber', 'settings.openchamber.desktopNetwork.field.launchAtLoginAria': '登入時自動啟動 OpenChamber', - 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '在背景啟動應用程式,不開啟視窗。按一下 Dock 圖示即可開啟。', + 'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '在背景啟動應用程式,不開啟視窗。可透過桌面狀態圖示開啟。', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': '將 OpenChamber 最小化和關閉到系統匣', + 'settings.openchamber.desktopNetwork.field.minimizeToTray': '最小化和關閉到系統匣', + 'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': '主視窗最小化或關閉時,讓 OpenChamber 繼續在 Windows 系統匣中執行。', 'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'OpenChamber 執行時保持電腦喚醒', 'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber 執行時保持電腦喚醒', 'settings.openchamber.desktopNetwork.field.keepAwakeDescription': '讓手機可以持續開啟此應用程式。螢幕仍可關閉。', diff --git a/packages/ui/src/lib/persistence.ts b/packages/ui/src/lib/persistence.ts index 1d760f08..02afee54 100644 --- a/packages/ui/src/lib/persistence.ts +++ b/packages/ui/src/lib/persistence.ts @@ -747,6 +747,12 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => { if (typeof candidate.desktopLanAccessEnabled === 'boolean') { result.desktopLanAccessEnabled = candidate.desktopLanAccessEnabled; } + if (typeof candidate.desktopKeepAwakeEnabled === 'boolean') { + result.desktopKeepAwakeEnabled = candidate.desktopKeepAwakeEnabled; + } + if (typeof candidate.desktopMinimizeToTrayEnabled === 'boolean') { + result.desktopMinimizeToTrayEnabled = candidate.desktopMinimizeToTrayEnabled; + } const projects = sanitizeProjects(candidate.projects); if (projects) { diff --git a/packages/ui/src/lib/settings/search.ts b/packages/ui/src/lib/settings/search.ts index f57ad5d2..f25486ce 100644 --- a/packages/ui/src/lib/settings/search.ts +++ b/packages/ui/src/lib/settings/search.ts @@ -22,6 +22,8 @@ interface SettingsSearchAvailabilityContext extends SettingsRuntimeContext { isDesktopLocalOrigin: boolean; // macOS desktop shell — for controls that only render on darwin (e.g. dock badge). isMac: boolean; + // Windows desktop shell — for controls that only render on win32. + isWindows: boolean; } const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [ @@ -312,6 +314,14 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [ keywords: ['desktop', 'startup', 'login'], isAvailable: (ctx) => ctx.isDesktopLocalOrigin, }, + { + id: 'sessions.desktop-minimize-to-tray', + page: 'sessions', + titleKey: 'settings.openchamber.desktopNetwork.field.minimizeToTray', + descriptionKey: 'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription', + keywords: ['desktop', 'tray', 'system tray', 'minimize', 'close', 'background', 'windows'], + isAvailable: (ctx) => ctx.isDesktopLocalOrigin && ctx.isWindows, + }, { id: 'sessions.desktop-keep-awake', page: 'sessions', diff --git a/packages/web/server/lib/opencode/settings-helpers.js b/packages/web/server/lib/opencode/settings-helpers.js index 056a5f04..49abc179 100644 --- a/packages/web/server/lib/opencode/settings-helpers.js +++ b/packages/web/server/lib/opencode/settings-helpers.js @@ -181,6 +181,9 @@ export const createSettingsHelpers = (dependencies) => { if (typeof candidate.desktopKeepAwakeEnabled === 'boolean') { result.desktopKeepAwakeEnabled = candidate.desktopKeepAwakeEnabled; } + if (typeof candidate.desktopMinimizeToTrayEnabled === 'boolean') { + result.desktopMinimizeToTrayEnabled = candidate.desktopMinimizeToTrayEnabled; + } if (typeof candidate.desktopUiPassword === 'string') { result.desktopUiPassword = candidate.desktopUiPassword.trim(); } diff --git a/packages/web/server/lib/opencode/settings-helpers.test.js b/packages/web/server/lib/opencode/settings-helpers.test.js index d030d1f0..7560f857 100644 --- a/packages/web/server/lib/opencode/settings-helpers.test.js +++ b/packages/web/server/lib/opencode/settings-helpers.test.js @@ -100,6 +100,17 @@ describe('settings helpers', () => { }); }); + it('accepts desktopMinimizeToTrayEnabled as a persisted shared setting', () => { + const helpers = createTestHelpers(); + + expect(helpers.sanitizeSettingsUpdate({ desktopMinimizeToTrayEnabled: true })).toEqual({ + desktopMinimizeToTrayEnabled: true, + }); + expect(helpers.sanitizeSettingsUpdate({ desktopMinimizeToTrayEnabled: false })).toEqual({ + desktopMinimizeToTrayEnabled: false, + }); + }); + it('accepts desktopUiPassword as a persisted shared setting', () => { const helpers = createTestHelpers();