Fix the composer scroller flash, edit focus, and Shift+Return

- The composer only enables its scroller once the text is taller than the
  seven-line max, so it no longer flashes on every growth step.
- The composer re-asserts first responder only when canStealFocus is true;
  it is off while a modal field or the sync overlay has the keyboard, so
  typing in the edit dialog, search and sync fields no longer bounces back.
- The edit dialog is a ComposerView now: Return saves, Shift+Return inserts
  a newline, Esc cancels, and paste uses the same path as the composer.
- Shift+Return never actually worked: returning false from doCommandBy does
  not fall through to the default newline insertion, so the event died. The
  newline is inserted explicitly and the Shift state comes from the keyDown.
This commit is contained in:
2026-10-23 22:54:04 +08:00
parent d665d493af
commit a75d6fdf41
3 changed files with 74 additions and 17 deletions
+16 -1
View File
@@ -44,7 +44,12 @@ clicking an item copies it back to the clipboard.
newline, Esc forwards to the panel (clear selection, else hide). Height is
measured from the text and pushed into a `height` binding the panel applies
as a frame — a representable cannot resize itself through
`intrinsicContentSize` once SwiftUI offers a larger size.
`intrinsicContentSize` once SwiftUI offers a larger size. The vertical
scroller is disabled until the text is taller than the max height, so it
never flashes during growth. The composer re-asserts first responder on
updates only while `canStealFocus` is true; the panel turns it off whenever
a modal field (search, edit, new workspace) or the sync overlay owns the
keyboard — otherwise every render stole focus back mid-edit.
- `Sources/Prompt/PanelView.swift` — the panel UI. Header (workspace switcher,
search, add photos), a segmented Workspace/All tab (All lists every prompt
labeled with its workspace), item list, in-panel edit modal, composer.
@@ -221,6 +226,16 @@ brew upgrade --cask prompt
firing. The tap is re-enabled on `tapDisabledByTimeout`.
- `Store` is `@MainActor`; anything reaching it from monitors/callbacks must
go through `Task { @MainActor in }`.
- **The composer steals focus via `updateNSView`, so gate it.** It used to
re-assert first responder on every render, which yanked focus out of the
edit dialog (and search/sync fields) on every keystroke. The gate is
`canStealFocus`; any new modal text field must be included in it.
- **`doCommandBy` has no fall-through once the delegate implements it.**
Returning false for `insertNewline` on Shift+Return killed the event instead
of inserting a newline, so Shift+Return never actually worked. The composer
now inserts it explicitly, and reads the Shift state from the `keyDown`
currently being handled because `NSApp.currentEvent` is not reliably
populated when the command fires.
- `captureSelection()` blocks up to 0.5s polling the pasteboard — keep it off
the main thread (the hotkey handler already dispatches to a global queue).
- The double-Shift tap needs Accessibility; without it the event tap silently