From 3214b79854abf689d82ce2bd3b4dc233b9f344fd Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Thu, 4 Jun 2026 18:43:07 +0300 Subject: [PATCH] fix: integrate mini chat window controls on Windows --- packages/electron/main.mjs | 4 +- .../desktop/WindowsWindowControls.tsx | 83 +++++++++++++++++++ packages/ui/src/components/layout/Header.tsx | 78 +---------------- .../components/mini-chat/MiniChatLayout.tsx | 7 +- 4 files changed, 93 insertions(+), 79 deletions(-) create mode 100644 packages/ui/src/components/desktop/WindowsWindowControls.tsx diff --git a/packages/electron/main.mjs b/packages/electron/main.mjs index 2c51c1b8..1ea1100c 100644 --- a/packages/electron/main.mjs +++ b/packages/electron/main.mjs @@ -2101,7 +2101,9 @@ const createMiniChatWindow = async ({ mode, sessionId = '', directory = '', proj icon: getWindowIconPath(), show: false, backgroundColor: '#151313', - titleBarStyle: process.platform === 'darwin' ? 'hidden' : 'default', + frame: process.platform === 'win32' ? false : undefined, + autoHideMenuBar: process.platform !== 'darwin', + titleBarStyle: process.platform === 'darwin' || process.platform === 'win32' ? 'hidden' : 'default', trafficLightPosition: process.platform === 'darwin' ? { x: 16, y: 17 } : undefined, webPreferences: { additionalArguments: [ diff --git a/packages/ui/src/components/desktop/WindowsWindowControls.tsx b/packages/ui/src/components/desktop/WindowsWindowControls.tsx new file mode 100644 index 00000000..153850cb --- /dev/null +++ b/packages/ui/src/components/desktop/WindowsWindowControls.tsx @@ -0,0 +1,83 @@ +import React, { useEffect } from 'react'; + +import { Icon } from '@/components/icon/Icon'; +import { useI18n } from '@/lib/i18n'; +import { cn } from '@/lib/utils'; +import { invokeDesktop } from '@/lib/desktop'; + +type WindowsWindowControlsProps = { + visible: boolean; +}; + +export const WindowsWindowControls = React.memo(function WindowsWindowControls({ visible }: WindowsWindowControlsProps) { + const { t } = useI18n(); + const [isMaximized, setIsMaximized] = React.useState(false); + + useEffect(() => { + if (!visible) { + return; + } + + let disposed = false; + void invokeDesktop<{ maximized?: boolean }>('desktop_get_current_window_state') + .then((state) => { + if (!disposed) { + setIsMaximized(Boolean(state?.maximized)); + } + }) + .catch(() => {}); + + const handleMaximizedChange = (event: Event) => { + const detail = (event as CustomEvent<{ maximized?: boolean }>).detail; + setIsMaximized(Boolean(detail?.maximized)); + }; + + window.addEventListener('openchamber:window-maximized-changed', handleMaximizedChange); + return () => { + disposed = true; + window.removeEventListener('openchamber:window-maximized-changed', handleMaximizedChange); + }; + }, [visible]); + + if (!visible) { + return null; + } + + const buttonClassName = 'app-region-no-drag inline-flex h-12 w-11 items-center justify-center text-muted-foreground transition-colors hover:bg-interactive-hover hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary'; + + return ( +
+ + + +
+ ); +}); diff --git a/packages/ui/src/components/layout/Header.tsx b/packages/ui/src/components/layout/Header.tsx index 2c3b850d..67bce021 100644 --- a/packages/ui/src/components/layout/Header.tsx +++ b/packages/ui/src/components/layout/Header.tsx @@ -32,6 +32,7 @@ import { useFeatureFlagsStore } from '@/stores/useFeatureFlagsStore'; import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { ContextUsageDisplay } from '@/components/ui/ContextUsageDisplay'; +import { WindowsWindowControls } from '@/components/desktop/WindowsWindowControls'; import { UpdateDialog } from '@/components/ui/UpdateDialog'; import { useDeviceInfo, useTabletStandalonePwaRuntime } from '@/lib/device'; import { cn, hasModifier } from '@/lib/utils'; @@ -127,83 +128,6 @@ const HeaderIconActionButton = React.memo(function HeaderIconActionButton({ ); }); -type WindowsWindowControlsProps = { - visible: boolean; -}; - -const WindowsWindowControls = React.memo(function WindowsWindowControls({ visible }: WindowsWindowControlsProps) { - const { t } = useI18n(); - const [isMaximized, setIsMaximized] = React.useState(false); - - useEffect(() => { - if (!visible) { - return; - } - - let disposed = false; - void invokeDesktop<{ maximized?: boolean }>('desktop_get_current_window_state') - .then((state) => { - if (!disposed) { - setIsMaximized(Boolean(state?.maximized)); - } - }) - .catch(() => {}); - - const handleMaximizedChange = (event: Event) => { - const detail = (event as CustomEvent<{ maximized?: boolean }>).detail; - setIsMaximized(Boolean(detail?.maximized)); - }; - - window.addEventListener('openchamber:window-maximized-changed', handleMaximizedChange); - return () => { - disposed = true; - window.removeEventListener('openchamber:window-maximized-changed', handleMaximizedChange); - }; - }, [visible]); - - if (!visible) { - return null; - } - - const buttonClassName = 'app-region-no-drag inline-flex h-12 w-11 items-center justify-center text-muted-foreground transition-colors hover:bg-interactive-hover hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary'; - - return ( -
- - - -
- ); -}); - type DesktopGitHubControlProps = { isMobile: boolean; githubAuthStatus: GitHubAuthStatus | null; diff --git a/packages/ui/src/components/mini-chat/MiniChatLayout.tsx b/packages/ui/src/components/mini-chat/MiniChatLayout.tsx index 5d5002c2..bff69eb7 100644 --- a/packages/ui/src/components/mini-chat/MiniChatLayout.tsx +++ b/packages/ui/src/components/mini-chat/MiniChatLayout.tsx @@ -3,6 +3,7 @@ import { Button } from '@/components/ui/button'; import { ChatContainer } from '@/components/chat/ChatContainer'; import { ChatSurfaceProvider } from '@/components/chat/ChatSurfaceContext'; import { ContextUsageDisplay } from '@/components/ui/ContextUsageDisplay'; +import { WindowsWindowControls } from '@/components/desktop/WindowsWindowControls'; import { SessionSwitcherDropdown } from '@/components/session/SessionSwitcherDropdown'; import { cn } from '@/lib/utils'; import { useI18n } from '@/lib/i18n'; @@ -69,6 +70,9 @@ const MiniChatHeader: React.FC<{ mode: MiniChatMode }> = ({ mode }) => { const [pinned, setPinned] = React.useState(false); const macosMajor = typeof window !== 'undefined' ? window.__OPENCHAMBER_MACOS_MAJOR__ ?? 0 : 0; const hasMacTrafficLights = Number.isFinite(macosMajor) && macosMajor > 0; + const isWindowsElectronDesktop = typeof window !== 'undefined' + && Boolean(window.__OPENCHAMBER_ELECTRON__) + && window.__OPENCHAMBER_PLATFORM__ === 'win32'; const macosHeaderSizeClass = hasMacTrafficLights ? macosMajor >= 26 ? 'h-12' @@ -258,7 +262,7 @@ const MiniChatHeader: React.FC<{ mode: MiniChatMode }> = ({ mode }) => { className={cn( 'flex items-center gap-3 border-b border-[var(--interactive-border)] bg-sidebar pr-3', hasMacTrafficLights ? 'pl-[5.5rem]' : 'pl-3', - macosHeaderSizeClass || 'min-h-14', + isWindowsElectronDesktop ? 'h-12' : macosHeaderSizeClass || 'min-h-14', )} style={dragRegionStyle} > @@ -327,6 +331,7 @@ const MiniChatHeader: React.FC<{ mode: MiniChatMode }> = ({ mode }) => { > + ); };