Make pasting reliable and turn items into checkboxes

Pasting still failed because the panel is a nonactivating panel: it can
be key while the app is not active, and menu key equivalents are not
delivered reliably in that state. Cmd+V is now dispatched to the
responder chain directly, and the app no longer replaces SwiftUI's main
menu — it only adds Hide Panel to it, so the Edit items that AppKit
routes text shortcuts through stay intact.

Rows also gained a checkbox so a workspace works as a checklist without
turning the row into a button, which would swallow the checkbox's clicks.
This commit is contained in:
2026-10-09 17:49:06 +08:00
parent 93cd6f2056
commit 9f0c0f8113
5 changed files with 80 additions and 71 deletions
+18 -36
View File
@@ -32,7 +32,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
// which locks the user out once the panel is hidden.
if CommandLine.arguments.contains("--diag") {
setupStatusItem()
setupMainMenu()
installMenuItems()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.writeDiagnostics()
NSApp.terminate(nil)
@@ -60,9 +60,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
HotkeyManager.shared.start()
setupStatusItem()
setupMainMenu()
promptForAccessibilityIfNeeded()
// Let SwiftUI finish installing its main menu, then augment it.
DispatchQueue.main.async { self.installMenuItems() }
// Show the panel on launch so the app is discoverable (it has no Dock
// icon and the menu bar icon can be hard to spot on a full bar).
Task { @MainActor in
@@ -111,44 +113,24 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
return .terminateNow
}
/// Accessory apps have no visible menu bar, but the main menu still
/// services key equivalents — this is what makes Cmd+Q and Cmd+W work
/// while the panel has focus.
///
/// The Edit menu is not decoration: AppKit routes Cmd+C/V/X/A/Z through
/// these items, so a minimal main menu silently breaks pasting and
/// selecting in the composer.
private func setupMainMenu() {
let mainMenu = NSMenu()
/// Adds "Hide Panel" to the menu SwiftUI already installed. Do NOT replace
/// `NSApp.mainMenu`: SwiftUI's menu carries the Edit items that AppKit
/// routes Cmd+C/V/X/A/Z through, so replacing it breaks text editing in the
/// composer. This only inserts one item and drops SwiftUI's Settings item,
/// whose empty scene would otherwise shadow the in-panel Cmd+, sync panel.
private func installMenuItems() {
guard let appMenu = NSApp.mainMenu?.items.first?.submenu else { return }
let appMenuItem = NSMenuItem()
mainMenu.addItem(appMenuItem)
let appMenu = NSMenu(title: "Prompt")
appMenuItem.submenu = appMenu
if let settings = appMenu.items.firstIndex(where: { $0.keyEquivalent == "," && $0.keyEquivalentModifierMask == [.command] }) {
appMenu.removeItem(at: settings)
}
guard !appMenu.items.contains(where: { $0.action == #selector(hidePanel) }) else { return }
let hide = NSMenuItem(title: "Hide Panel", action: #selector(hidePanel), keyEquivalent: "w")
hide.target = self
appMenu.addItem(hide)
appMenu.addItem(.separator())
let quit = NSMenuItem(title: "Quit Prompt", action: #selector(quit), keyEquivalent: "q")
quit.target = self
appMenu.addItem(quit)
let editMenuItem = NSMenuItem()
mainMenu.addItem(editMenuItem)
let editMenu = NSMenu(title: "Edit")
editMenuItem.submenu = editMenu
editMenu.addItem(withTitle: "Undo", action: Selector(("undo:")), keyEquivalent: "z")
let redo = editMenu.addItem(withTitle: "Redo", action: Selector(("redo:")), keyEquivalent: "z")
redo.keyEquivalentModifierMask = [.command, .shift]
editMenu.addItem(.separator())
editMenu.addItem(withTitle: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x")
editMenu.addItem(withTitle: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c")
editMenu.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v")
editMenu.addItem(withTitle: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a")
NSApp.mainMenu = mainMenu
let quitIndex = appMenu.items.firstIndex { $0.action == #selector(NSApplication.terminate(_:)) } ?? appMenu.items.count
appMenu.insertItem(.separator(), at: quitIndex)
appMenu.insertItem(hide, at: quitIndex + 1)
}
@MainActor @objc private func hidePanel() {