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
+15 -8
View File
@@ -156,14 +156,21 @@ brew upgrade --cask prompt
## Gotchas
- **Setting `NSApp.mainMenu` replaces SwiftUI's default menu.** SwiftUI installs
a menu containing the standard Edit items, and AppKit routes Cmd+C/V/X/A/Z
through those items — so a hand-built menu with only Hide/Quit silently
breaks pasting, copying and select-all in the composer. `setupMainMenu()`
therefore rebuilds an Edit menu with `undo:`/`redo:`/`cut:`/`copy:`/`paste:`/
`selectAll:` (nil targets, so they travel the responder chain to the field
editor). `Prompt --diag` dumps the menu so this can be checked without a
keyboard.
- **Never assign `NSApp.mainMenu`.** SwiftUI installs a menu whose Edit items
are how AppKit routes Cmd+C/V/X/A/Z; replacing it with a hand-built menu
silently breaks pasting, copying and select-all in the composer (this shipped
once and had to be reverted). `installMenuItems()` only *augments* the
existing menu: it adds Hide Panel (Cmd+W) and removes SwiftUI's Settings item,
whose empty scene would otherwise shadow the in-panel Cmd+, sync panel.
- **Cmd+V is handled by the paste monitor, not the menu.** The panel is a
`.nonactivatingPanel`, so it can be key while the app is not active, and menu
key equivalents are not reliably delivered in that state. The monitor
dispatches `paste:` to the responder chain itself for text, and adds the
clipboard image to the store when there is no text. `Prompt --diag` dumps the
menu so the wiring can be checked without a keyboard.
- **A row is not a `Button`** because it contains a checkbox `Button` for
ticking items off, and nested buttons never receive clicks. The row uses
`contentShape` + `onTapGesture` for click-to-copy instead.
- **Never bind bare keys as `.keyboardShortcut` in the panel** (Space, Delete,
plain Cmd+C). SwiftUI key equivalents are matched before text input and
would steal characters from the composer TextField. Only Cmd-modified