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

Open
#620 0 comments 0 reactions 0 assignees View on GitHub
area:clipboard bug P2
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.