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();