From 1c709967f8f1b0eaa8b580b4d07fe57a450c654c Mon Sep 17 00:00:00 2001 From: PJ Date: Wed, 2 Sep 2026 19:12:19 +0530 Subject: [PATCH] fix few bugs --- docs/architecture.md | 9 ++++-- src-tauri/capabilities/desktop.json | 1 - src-tauri/src/lib.rs | 43 +++++++++++++++++++++++++++-- src-tauri/tauri.conf.json | 3 +- src/App.tsx | 7 +++-- src/components/Header.tsx | 16 ++--------- src/styles/app.css | 4 +++ 7 files changed, 61 insertions(+), 22 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index e4338d3..43a0c12 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -127,8 +127,13 @@ assume away. ## Platforms macOS uses the overlay title bar with the header row padded for the traffic lights, as margin -does. Linux has no traffic lights, so that padding is conditional. Linux builds need -`libwebkit2gtk-4.1-dev` and ship as AppImage and deb. +does. The lights are moved down to the header's centre line (`trafficLightPosition` in +`tauri.conf.json`), because AppKit places them for a 32pt title bar and the header is 46px. +Closing the window on macOS hides it rather than quitting, the way WhatsApp and Slack do: the +process stays in the Dock with sync running, a Dock click brings the window back, and Cmd+Q +quits. Both halves live in `lib.rs`. Linux has no traffic lights, so that padding is +conditional, and closing the window there quits. Linux builds need `libwebkit2gtk-4.1-dev` and +ship as AppImage and deb. ## Order of work diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json index b0aa0b6..cf5cf1b 100644 --- a/src-tauri/capabilities/desktop.json +++ b/src-tauri/capabilities/desktop.json @@ -7,7 +7,6 @@ "permissions": [ "core:window:allow-destroy", "core:window:allow-start-dragging", - "core:window:allow-toggle-maximize", "updater:default", "process:allow-restart" ] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 9abbdbc..9a06688 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -219,6 +219,20 @@ fn watch_for_the_consent_tab_closing(window: &tauri::WebviewWindow) { }); } +/// The other half of closing on macOS. The red button and Cmd+W hide the window rather than +/// destroy it (`on_window_event` in `run`), the way WhatsApp and Slack do: the app stays in the +/// Dock with sync still running, and Cmd+Q is what quits. AppKit does nothing of its own for a +/// Dock click when it can see no window, so bringing it back is on us. `unminimize` covers the +/// yellow button, which lands here for the same reason. +#[cfg(target_os = "macos")] +fn show_main_window(app: &tauri::AppHandle) { + if let Some(window) = app.get_webview_window("main") { + let _ = window.unminimize(); + let _ = window.show(); + let _ = window.set_focus(); + } +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { // generate_context! first, so the updater plugin registers only when the merged config @@ -286,7 +300,17 @@ pub fn run() { }); } - builder + #[cfg(target_os = "macos")] + { + builder = builder.on_window_event(|window, event| { + if let tauri::WindowEvent::CloseRequested { api, .. } = event { + api.prevent_close(); + let _ = window.hide(); + } + }); + } + + let app = builder .invoke_handler(tauri::generate_handler![ google::accounts_list, google::account_connect, @@ -301,6 +325,19 @@ pub fn run() { sync::sync_status, sync::sync_flush ]) - .run(context) - .expect("error while running Margin Calendar"); + .build(context) + .expect("error while building Margin Calendar"); + + app.run(|app, event| { + #[cfg(target_os = "macos")] + if let tauri::RunEvent::Reopen { + has_visible_windows: false, + .. + } = event + { + show_main_window(app); + } + #[cfg(not(target_os = "macos"))] + let _ = (app, event); + }); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3f5db0d..fd11440 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -17,7 +17,8 @@ "height": 900, "minWidth": 880, "minHeight": 560, - "titleBarStyle": "Overlay" + "titleBarStyle": "Overlay", + "trafficLightPosition": { "x": 9, "y": 25 } } ], "security": { diff --git a/src/App.tsx b/src/App.tsx index 55976d8..a4d6280 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,7 +27,7 @@ import { useSync } from "./store/useSync"; import { notify } from "./store/useToast"; import { useCompact, usePhone, useTouch } from "./useMedia"; import { syncFlush } from "./api/sync"; -import { isDesktop, isTauri, live, type AuthEvent, type SyncStatus } from "./ipc"; +import { isDesktop, isMacDesktop, isTauri, live, type AuthEvent, type SyncStatus } from "./ipc"; /** Long enough for sync_flush's own 1s lock wait plus its 4s drain budget. */ const FLUSH_TIMEOUT_MS = 5500; @@ -91,8 +91,11 @@ function App() { // `isDesktop` genuinely means desktop here: there is no close to intercept on a phone, and the // window commands live in a capability that platform does not get, so this would be a rejected // IPC call rather than a no-op. The outbox still drains on the next launch either way. + // + // Not on macOS, where closing the window hides it (lib.rs) and the process, sync loop included, + // carries on behind it. A listener here would be the thing that destroyed the window instead. useEffect(() => { - if (!isDesktop) return; + if (!isDesktop || isMacDesktop) return; const win = getCurrentWindow(); const unlisten = win.onCloseRequested(async (event) => { if (useSync.getState().pendingWrites === 0) return; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 95e7cd6..5318b13 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,8 +1,6 @@ // The only persistent chrome. It carries the date range, the view switcher and the current time // range, and on macOS the traffic lights float over it, so it costs no extra height. -import { getCurrentWindow } from "@tauri-apps/api/window"; -import type { MouseEvent as ReactMouseEvent } from "react"; import { Icon } from "./Icon"; import { useCalendarView, spanFor, type View } from "../store/useCalendarView"; import { useOverlays } from "../store/useOverlays"; @@ -10,7 +8,6 @@ import { useSync } from "../store/useSync"; import { useTheme } from "../store/useTheme"; import { addDays, formatRange } from "../time"; import { keyLabel, keysFor } from "../keys/bindings"; -import { isDesktop } from "../ipc"; import type { CommandId } from "../keys/commands"; // Exported where the phone bar shows the same thing: two rows of chrome that disagreed about which @@ -55,17 +52,10 @@ export function Header() { const { from, to } = spanFor(view, anchor); const step = moveDay; - // Double click to zoom, the way a native title bar does. Tauri's drag region only handles this - // when the header itself is the event target, and the layout children cover it end to end, so - // it is wired explicitly. A double click on a control is not a title bar gesture. - const onDoubleClick = (e: ReactMouseEvent) => { - if (!isDesktop) return; - if ((e.target as HTMLElement).closest("button, input, select, a")) return; - void getCurrentWindow().toggleMaximize(); - }; - + // Dragging and double click to zoom both come from Tauri's drag region script, which skips + // buttons on its own. A second toggle from here would undo the first. return ( -
+