From 833d570024b971a9e175acd30146dda92dfc884d Mon Sep 17 00:00:00 2001 From: blankll Date: Sat, 20 Jun 2026 16:07:18 +0800 Subject: [PATCH 1/4] feat(window): add tauri-plugin-window-state dependency Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src-tauri/Cargo.lock | 16 ++++++++++++++++ src-tauri/Cargo.toml | 1 + 2 files changed, 17 insertions(+) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 69d79b41..7cd2b23d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -6423,6 +6423,7 @@ dependencies = [ "tauri-plugin-os", "tauri-plugin-store", "tauri-plugin-updater", + "tauri-plugin-window-state", "thiserror 1.0.69", "tiberius", "tokio", @@ -6984,6 +6985,21 @@ dependencies = [ "zip 4.6.1", ] +[[package]] +name = "tauri-plugin-window-state" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73736611e14142408d15353e21e3cca2f12a3cfb523ad0ce85999b6d2ef1a704" +dependencies = [ + "bitflags 2.13.0", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", +] + [[package]] name = "tauri-runtime" version = "2.11.3" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e109cc37..0c5f060c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -66,6 +66,7 @@ tauri-plugin-store = "2" tauri-plugin-dialog = "2" tauri-plugin-deep-link = "2" tauri-plugin-os = "2" +tauri-plugin-window-state = "2" url = "2" base64 = "0.22" chrono = "0.4" From 7cdfb6331085257bf5a6893a1a0af06996b026ac Mon Sep 17 00:00:00 2001 From: blankll Date: Sat, 20 Jun 2026 16:07:24 +0800 Subject: [PATCH 2/4] feat(window): register window-state plugin and configure custom window decorations - Register tauri-plugin-window-state for window position/size persistence - Set visible: false to prevent flash before full initialization - Remove native decorations on non-macOS via set_decorations(false) - Add window permissions for minimize/maximize/close/fullscreen control Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src-tauri/capabilities/default.json | 9 +++++++++ src-tauri/src/lib.rs | 13 +++++++++++++ src-tauri/tauri.conf.json | 3 ++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 8591ac90..6f58b933 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -5,6 +5,15 @@ "windows": ["main"], "permissions": [ "core:default", + "core:window:default", + "core:window:allow-show", + "core:window:allow-minimize", + "core:window:allow-toggle-maximize", + "core:window:allow-close", + "core:window:allow-is-maximized", + "core:window:allow-is-fullscreen", + "core:window:allow-start-dragging", + "window-state:default", "opener:default", "store:default", "dialog:default", diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 248a9c24..1d4abbae 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -90,6 +90,7 @@ pub fn run() { .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_os::init()) + .plugin(tauri_plugin_window_state::Builder::default().build()) .manage(app_state.clone()) .manage(store.clone()) .setup(move |app| { @@ -160,6 +161,18 @@ pub fn run() { } } + // On non-macOS, remove native window decorations BEFORE showing + // the window so there is no flash of the native title bar + #[cfg(not(target_os = "macos"))] + if let Some(window) = app.get_webview_window("main") { + let _ = window.set_decorations(false); + } + + // Show the window after full initialization + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + } + Ok(()) }) .invoke_handler(tauri::generate_handler![ diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 5a39ccd8..82bf951e 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -20,7 +20,8 @@ "x": 16, "y": 18 }, - "hiddenTitle": true + "hiddenTitle": true, + "visible": false } ], "security": { From d7e39a20159135811200c1bd2468837c499e059a Mon Sep 17 00:00:00 2001 From: blankll Date: Sat, 20 Jun 2026 16:07:30 +0800 Subject: [PATCH 3/4] feat(window): add window controls composable and platform detection - useWindowControls: composable for minimize/maximize/close actions and state tracking - usePlatform: add platformReady ref to guard platform-dependent UI - Export new composables from index - Add unit tests for shouldReserveMacTrafficLightInset and useWindowControls Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/__tests__/useWindowControls.test.ts | 89 +++++++++++++++++++++++++ src/composables/index.ts | 1 + src/composables/usePlatform.ts | 5 ++ src/composables/useWindowControls.ts | 55 +++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 src/__tests__/useWindowControls.test.ts create mode 100644 src/composables/useWindowControls.ts diff --git a/src/__tests__/useWindowControls.test.ts b/src/__tests__/useWindowControls.test.ts new file mode 100644 index 00000000..6aa72f6b --- /dev/null +++ b/src/__tests__/useWindowControls.test.ts @@ -0,0 +1,89 @@ +/** + * @jest-environment node + */ +import { shouldReserveMacTrafficLightInset } from '@/composables/useWindowControls' + +describe('shouldReserveMacTrafficLightInset', () => { + it('returns true on macOS when not fullscreen', () => { + expect(shouldReserveMacTrafficLightInset(true, false, true)).toBe(true) + }) + + it('returns false on macOS when fullscreen', () => { + expect(shouldReserveMacTrafficLightInset(true, true, true)).toBe(false) + }) + + it('returns false on non-macOS even when not fullscreen', () => { + expect(shouldReserveMacTrafficLightInset(false, false, true)).toBe(false) + }) + + it('returns false on non-macOS fullscreen', () => { + expect(shouldReserveMacTrafficLightInset(false, true, true)).toBe(false) + }) + + it('returns false in browser (non-desktop) context', () => { + expect(shouldReserveMacTrafficLightInset(true, false, false)).toBe(false) + }) +}) + +describe('useWindowControls', () => { + const mockMinimize = jest.fn() + const mockToggleMaximize = jest.fn() + const mockClose = jest.fn() + const mockIsMaximized = jest.fn() + const mockIsFullscreen = jest.fn() + const mockOnResized = jest.fn() + + beforeEach(() => { + jest.resetModules() + jest.mock('@tauri-apps/api/window', () => ({ + getCurrentWindow: () => ({ + minimize: mockMinimize, + toggleMaximize: mockToggleMaximize, + close: mockClose, + isMaximized: mockIsMaximized, + isFullscreen: mockIsFullscreen, + onResized: mockOnResized, + }), + })) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + it('executes minimize action', async () => { + const { useWindowControls } = await import('@/composables/useWindowControls') + const controls = useWindowControls() + await controls.minimize() + expect(mockMinimize).toHaveBeenCalled() + }) + + it('executes toggleMaximize action', async () => { + const { useWindowControls } = await import('@/composables/useWindowControls') + const controls = useWindowControls() + await controls.toggleMaximize() + expect(mockToggleMaximize).toHaveBeenCalled() + }) + + it('executes close action', async () => { + const { useWindowControls } = await import('@/composables/useWindowControls') + const controls = useWindowControls() + await controls.close() + expect(mockClose).toHaveBeenCalled() + }) + + it('tracks isMaximized and isFullscreen state', async () => { + mockIsMaximized.mockResolvedValue(true) + mockIsFullscreen.mockResolvedValue(false) + mockOnResized.mockResolvedValue(jest.fn()) + + const { useWindowControls } = await import('@/composables/useWindowControls') + const controls = useWindowControls() + + // The composable refreshes state on mount via onMounted. + // Since we're in a test environment outside Vue, onMounted doesn't fire. + // We verify that the default state is sensible. + expect(controls.isMaximized.value).toBe(false) + expect(controls.isFullscreen.value).toBe(false) + }) +}) diff --git a/src/composables/index.ts b/src/composables/index.ts index 9faa9ffd..853a9a88 100644 --- a/src/composables/index.ts +++ b/src/composables/index.ts @@ -5,3 +5,4 @@ export { useDataGridSelection } from './useDataGridSelection' export { useDataGridSort } from './useDataGridSort' export { useDataStudioChatAgent } from './useDataStudioChatAgent' export { formatSql, resolveDialect, useSqlFormatter } from './useSqlFormatter' +export { shouldReserveMacTrafficLightInset, useWindowControls } from './useWindowControls' diff --git a/src/composables/usePlatform.ts b/src/composables/usePlatform.ts index b5c06d05..98fb994c 100644 --- a/src/composables/usePlatform.ts +++ b/src/composables/usePlatform.ts @@ -2,6 +2,7 @@ import { platform } from '@tauri-apps/plugin-os' import { computed, ref } from 'vue' const platformCache = ref(null) +const platformReady = ref(false) async function getPlatform(): Promise { if (platformCache.value) { @@ -10,9 +11,12 @@ async function getPlatform(): Promise { try { const p = await platform() platformCache.value = p + platformReady.value = true return p } catch { + platformCache.value = 'unknown' + platformReady.value = true return 'unknown' } } @@ -42,6 +46,7 @@ function usePlatform() { modifierKey, altKey, platform: platformCache, + platformReady, } } diff --git a/src/composables/useWindowControls.ts b/src/composables/useWindowControls.ts new file mode 100644 index 00000000..c9e1a7f3 --- /dev/null +++ b/src/composables/useWindowControls.ts @@ -0,0 +1,55 @@ +import { getCurrentWindow } from '@tauri-apps/api/window' +import { onMounted, onUnmounted, ref } from 'vue' + +export function shouldReserveMacTrafficLightInset( + isMac: boolean, + isFullscreen: boolean, + isDesktop = true, +): boolean { + return isDesktop && isMac && !isFullscreen +} + +export function useWindowControls() { + const isMaximized = ref(false) + const isFullscreen = ref(false) + + let unlistenResize: (() => void) | null = null + + async function refreshState() { + try { + const window = getCurrentWindow() + const [maximized, fullscreen] = await Promise.all([ + window.isMaximized(), + window.isFullscreen(), + ]) + isMaximized.value = maximized + isFullscreen.value = fullscreen + } + catch { + // Not in Tauri environment — leave defaults + } + } + + const minimize = () => getCurrentWindow().minimize() + const toggleMaximize = () => getCurrentWindow().toggleMaximize() + const close = () => getCurrentWindow().close() + + onMounted(async () => { + await refreshState() + try { + const unlisten = await getCurrentWindow().onResized(() => { + refreshState() + }) + unlistenResize = unlisten + } + catch { + // Not in Tauri environment + } + }) + + onUnmounted(() => { + unlistenResize?.() + }) + + return { isMaximized, isFullscreen, minimize, toggleMaximize, close } +} From 5bf7cdb31e3d890147bae04cfe049cbc9a05563d Mon Sep 17 00:00:00 2001 From: blankll Date: Sat, 20 Jun 2026 16:07:36 +0800 Subject: [PATCH 4/4] feat(window): add custom window controls UI and draggable regions - WindowControls.vue: minimize/maximize/close buttons for non-macOS platforms - AppHeader.vue: conditional traffic light inset on macOS, double-click to maximize, WindowControls integration - AppSidebar.vue: add data-tauri-drag-region to logo area for window dragging Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/components/layout/AppHeader.vue | 20 +++++++++++- src/components/layout/AppSidebar.vue | 2 +- src/components/layout/WindowControls.vue | 41 ++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/components/layout/WindowControls.vue diff --git a/src/components/layout/AppHeader.vue b/src/components/layout/AppHeader.vue index 5cf2a189..c17d3452 100644 --- a/src/components/layout/AppHeader.vue +++ b/src/components/layout/AppHeader.vue @@ -1,8 +1,12 @@