uttrflow / uttrflow/uttrflow-swift
Clicking a clip's insert button skips the check for an application that quit behind the panel, so the clip is typed into whatever app comes forward
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 277
Description
## What happens
The clipboard panel guards against the application under it quitting while the panel is open (A8). When Return is pressed, `QuickPanelController.relay` hands over the application that owned the caret when the panel opened (`Sources/Uttrflow/Panel/QuickPanelController.swift:194-197`), and `AppDelegate.panelAnswered(_:behind:)` downgrades the insertion to copy-only if it has terminated (`Sources/Uttrflow/AppDelegate.swift:824-828`):
```swift
if let behind, behind.isTerminated, panel?.insertion == .atCaret {
panel?.insertion = .clipboardOnly(.nothingFocused)
}
```
A click on a row's insert button takes another route. `onIntent` carries no caret owner (`QuickPanelController.swift:189`), and `AppDelegate.carryOut(_ intent: PanelIntent)` forwards `.insert(id)` straight to `panelAnswered(key)` without the check (`AppDelegate.swift:946-950`; `PanelIntent.key` maps `.insert` to `.choose`, `Sources/UttrflowUX/PanelPresentation.swift:48`). The snapshot still says `.atCaret`, so the effect is `.closeAndInsert` (or `.closeAndInsertFormatted` for a clip with a rich form): the panel closes and `clipInserter.insert` types or pastes into whichever application macOS brought to the front after the quit. A failed insertion is only logged (`AppDelegate.swift:1075-1086`).
## Why it matters
Clips include passwords, tokens and card numbers (the panel masks them for that reason). Sending one into an application the user did not choose, for example a chat window that happened to be behind the one that quit, is the kind of mistake that cannot be undone. The keyboard path already does the right thing; only pointer users (and anyone using the row buttons with VoiceOver) take the unguarded route.
## How to reproduce
On a real Mac (the owner's machine was not used for this audit): open a text editor, open the clipboard panel over it, quit the editor with ⌘Q from the Dock menu while the panel stays up, then click Insert on a text row. Compare with pressing Return in the same situation, which copies and says so.
Code-level: the two calls into `panelAnswered` at `AppDelegate.swift:828` (after the check) and `:949` (without it).
## Acceptance criteria
- A click on a row's insert button goes through the same terminated-application check as Return.
- The check lives somewhere a test can reach (for example the caret owner's liveness is part of what `PanelSnapshot.applying` is given), with a test for both the key and the click route.
Contributor guide
Research direction
Start with QuickPanelController.swift:189 and :194-197, then compare AppDelegate.swift:824-828 with :946-950 and PanelPresentation.swift:48. Trace PanelSnapshot.applying and the panelAnswered routes, then add reachable tests for both Return and click insertion; done means a terminated caret owner causes copy-only behavior on both paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, swift
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100