mirror of
https://github.com/block/buzz.git
synced 2026-11-13 12:51:34 +00:00
feat(desktop): close Buzz window with Cmd+W (#6314)
## Summary - close the current Buzz window with `Cmd-W` on macOS - keep Buzz Term's existing `Cmd-W` behavior when the terminal owns input, where it closes the active terminal tab - cover the close-window chord and its modifier/composition guards with unit tests ## Why Buzz removes macOS's native Close Window menu accelerator so the webview can conditionally route `Cmd-W` to Buzz Term. That also left normal Buzz mode without the standard close-window shortcut. The new app-level bubble-phase handler restores window closing while allowing the terminal's capture-phase handler to retain priority. For the main window, the existing Rust close-request handler keeps the webview alive and hides it to the tray, matching the titlebar close button. ## Validation - desktop unit tests: 5,093 passed - focused close-window and terminal shortcut tests: 28 passed at `71a2cce42013440e297c7b237d45b20e9638078d` - desktop TypeScript: passed - desktop checks: passed (pre-existing warnings only) - workspace Rust clippy: passed - Tauri Rust clippy/check: passed - desktop build: passed - mobile format/analyze: passed - Tauri tests: 2,592 desktop tests and 26/27 terminal tests passed; the existing `default_prog_child_observes_the_login_argv0` PTY fixture repeatedly timed out waiting for `/bin/sh`, including when run alone, and is unrelated to this TypeScript shortcut change --------- Signed-off-by: Matt Kursmark <kursmark@squareup.com> Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -5,23 +5,19 @@
|
||||
//! `close_window` item in both the File and Window submenus, and muda gives
|
||||
//! that item a Cmd+W key equivalent bound to `performClose:`.
|
||||
//!
|
||||
//! Two consequences, both wrong for Buzz:
|
||||
//! That default cannot express Buzz's context-dependent behavior:
|
||||
//!
|
||||
//! 1. `CloseRequested` on the main window is intercepted in `lib.rs` and turned
|
||||
//! into hide-to-tray, so Cmd+W never closed a window -- it hid the whole
|
||||
//! app. That is already redundant with Cmd+H (Hide), which stays.
|
||||
//! into hide-to-tray. Cmd+W should take that path in normal Buzz mode.
|
||||
//! 2. macOS resolves a menu key equivalent before the webview receives any key
|
||||
//! event, so Buzz Term could never bind Cmd+W to "close this terminal tab"
|
||||
//! while the accelerator was claimed here.
|
||||
//!
|
||||
//! So this module builds the standard menu minus both `close_window` items.
|
||||
//! Everything else matches `Menu::default()` deliberately: the goal is to drop
|
||||
//! one item, not to design a menu.
|
||||
//!
|
||||
//! If hide-on-Cmd+W is ever wanted back in Buzz mode, the revisit path is to
|
||||
//! restore the item and disable it while the terminal owns input (a disabled
|
||||
//! item does not consume its key equivalent) -- at the cost of an owner->Rust
|
||||
//! IPC hop this approach does not need.
|
||||
//! Everything else matches `Menu::default()` deliberately. The webview routes
|
||||
//! Cmd+W conditionally instead: Buzz Term consumes it in capture phase while
|
||||
//! it owns input, and `useCloseWindowShortcut` closes the current window in
|
||||
//! normal Buzz mode.
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::menu::{
|
||||
|
||||
@@ -21,6 +21,7 @@ import { deriveShellRoute } from "@/app/AppShell.helpers";
|
||||
import { ThemeGrainientBackground } from "@/app/ThemeGrainientBackground";
|
||||
import { CommunityThemeController } from "@/shared/theme/CommunityThemeController";
|
||||
import { useReloadShortcut } from "@/app/useReloadShortcut";
|
||||
import { useCloseWindowShortcut } from "@/app/useCloseWindowShortcut";
|
||||
import { KnownAgentPubkeysProvider } from "@/features/agents/useKnownAgentPubkeys";
|
||||
import { huddleWindowChannelId } from "@/features/huddle/lib/huddleWindow";
|
||||
import { useAppOnboardingState } from "@/features/onboarding/hooks";
|
||||
@@ -769,6 +770,7 @@ function MachineBootstrap({ sharedIdentity }: { sharedIdentity: boolean }) {
|
||||
|
||||
export function App() {
|
||||
useReloadShortcut();
|
||||
useCloseWindowShortcut();
|
||||
useInitialRenderReady();
|
||||
const [sharedIdentity, setSharedIdentity] = useState<boolean | null>(null);
|
||||
const [queryClient] = useState(createBuzzQueryClient);
|
||||
|
||||
43
desktop/src/app/useCloseWindowShortcut.test.mjs
Normal file
43
desktop/src/app/useCloseWindowShortcut.test.mjs
Normal file
@@ -0,0 +1,43 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { isCloseWindowShortcut } from "./useCloseWindowShortcut.ts";
|
||||
|
||||
function chord(overrides = {}) {
|
||||
return {
|
||||
altKey: false,
|
||||
code: "KeyW",
|
||||
ctrlKey: false,
|
||||
defaultPrevented: false,
|
||||
isComposing: false,
|
||||
metaKey: true,
|
||||
repeat: false,
|
||||
shiftKey: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test("Cmd+W closes a window on macOS", () => {
|
||||
assert.equal(isCloseWindowShortcut(chord(), true), true);
|
||||
});
|
||||
|
||||
test("the close-window shortcut rejects other platforms and modified chords", () => {
|
||||
assert.equal(isCloseWindowShortcut(chord(), false), false);
|
||||
assert.equal(isCloseWindowShortcut(chord({ metaKey: false }), true), false);
|
||||
assert.equal(isCloseWindowShortcut(chord({ ctrlKey: true }), true), false);
|
||||
assert.equal(isCloseWindowShortcut(chord({ altKey: true }), true), false);
|
||||
assert.equal(isCloseWindowShortcut(chord({ shiftKey: true }), true), false);
|
||||
assert.equal(isCloseWindowShortcut(chord({ code: "KeyQ" }), true), false);
|
||||
});
|
||||
|
||||
test("handled, composing, and repeated events are left alone", () => {
|
||||
assert.equal(
|
||||
isCloseWindowShortcut(chord({ defaultPrevented: true }), true),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
isCloseWindowShortcut(chord({ isComposing: true }), true),
|
||||
false,
|
||||
);
|
||||
assert.equal(isCloseWindowShortcut(chord({ repeat: true }), true), false);
|
||||
});
|
||||
55
desktop/src/app/useCloseWindowShortcut.ts
Normal file
55
desktop/src/app/useCloseWindowShortcut.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import * as React from "react";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
|
||||
import { isMacPlatform } from "@/shared/lib/platform";
|
||||
|
||||
type CloseWindowChord = Pick<
|
||||
KeyboardEvent,
|
||||
| "altKey"
|
||||
| "code"
|
||||
| "ctrlKey"
|
||||
| "defaultPrevented"
|
||||
| "isComposing"
|
||||
| "metaKey"
|
||||
| "repeat"
|
||||
| "shiftKey"
|
||||
>;
|
||||
|
||||
export function isCloseWindowShortcut(
|
||||
event: CloseWindowChord,
|
||||
isMac: boolean,
|
||||
): boolean {
|
||||
return (
|
||||
isMac &&
|
||||
event.code === "KeyW" &&
|
||||
event.metaKey &&
|
||||
!event.ctrlKey &&
|
||||
!event.altKey &&
|
||||
!event.shiftKey &&
|
||||
!event.defaultPrevented &&
|
||||
!event.isComposing &&
|
||||
!event.repeat
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the standard macOS Cmd+W behavior without reclaiming the native
|
||||
* menu accelerator. Buzz Term handles the chord first in capture phase while
|
||||
* it owns input; otherwise this bubble-phase listener closes the current
|
||||
* window. The main window's Rust close handler turns that into hide-to-tray.
|
||||
*/
|
||||
export function useCloseWindowShortcut() {
|
||||
React.useEffect(() => {
|
||||
if (!isTauri()) return;
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (!isCloseWindowShortcut(event, isMacPlatform())) return;
|
||||
event.preventDefault();
|
||||
void getCurrentWindow().close();
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
}
|
||||
Reference in New Issue
Block a user