A file copied in Finder becomes a card's attachment — ⌘V's reserved clause finally answers

The image branch's precedence ladder always had a second clause: a file URL on the
pasteboard suppresses it, "a different gesture with a different answer" that the
code deliberately declined rather than guessed at. This fills it in: one or more
file URLs paste through the same `importAttachments` a Finder drop takes — one
collision ladder, one folder refusal (`FinderDrop.partition`), one set of banners
— outranking raw image data riding beside it (a Finder-copied image file carries
both; the actual file lands, not a re-encoded copy of its bytes) while still
deferring to the app's own clipboard type. Both ⌘V surfaces read the same
`ClipboardStore.fileURLPayload`, so the board's fallback and the card window's own
branch stay in step by construction rather than by two hand-kept-in-sync checks.

Fixed a real leak in the body editor's paste yield along the way: `public.file-url`
conforms to `public.url`, which `NSTextView` legitimately reads for a pasted
hyperlink, and `NSPasteboard.availableType(from:)` matches by conformance rather
than exact type — so a Finder copy carrying a generic URL representation beside
its file URL would have been silently swallowed as text and never reached the
window's attachment branch at all. The yield now declines outright on any
file-URL pasteboard before the generic capability check runs.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-09 08:38:07 -04:00
parent d0c546179f
commit 0fe92bdf38
9 changed files with 565 additions and 38 deletions
+21 -1
View File
@@ -775,7 +775,7 @@ struct PasteBoardBackgroundTests {
// MARK: - The body editor's paste yield
/// The responder behind the editor in these tests stands where the card window's
/// `cardWindowImagePaste` bridge stands in the app, and only counts.
/// `cardWindowPaste` bridge stands in the app, and only counts.
@MainActor
private final class PasteCatcher: NSView {
var pastes = 0
@@ -820,6 +820,26 @@ struct PasteYieldTests {
#expect(catcher.pastes == 1)
}
/// **The file-URL branch's own yield** a Finder copy reaches the window's attachment branch
/// exactly as a screenshot does, `readablePasteboardTypes`'s explicit exclusion of `.fileURL`
/// (`CardBodyTextView`'s override) proven rather than assumed: `importsGraphics` being `false` is
/// an AppKit default this pins down as a contract.
@Test("A file-URL-only pasteboard validates through the editor and forwards to the responder behind")
func fileURLOnlyYields() throws {
let catcher = PasteCatcher(frame: .zero)
let (editor, pasteboard) = makeEditor(behind: catcher)
defer { pasteboard.releaseGlobally() }
let fileURL = FileManager.default.temporaryDirectory
.appendingPathComponent("paste-yield-\(UUID().uuidString).png")
try Data([0x01]).write(to: fileURL)
defer { try? FileManager.default.removeItem(at: fileURL) }
pasteboard.writeObjects([fileURL as NSURL])
#expect(editor.validateUserInterfaceItem(pasteItem))
editor.paste(nil)
#expect(catcher.pastes == 1)
}
/// No expectation on `validateUserInterfaceItem` here: a readable pasteboard routes validation
/// to `super`, and `NSTextView`'s own answer reads the *machine's* general pasteboard asserting
/// it would tie the test to whatever the host's clipboard happens to hold. The claim under test