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:
@@ -32,6 +32,12 @@ final class FakePasteboard: ClipboardPasteboard {
|
||||
/// test can actually make.
|
||||
private var foreign: [(type: String, data: Data)] = []
|
||||
|
||||
/// **A multi-item read, unlike `foreign`** — the file-URL branch's own `fileURLs()`, which reads
|
||||
/// across every pasteboard item rather than the first item's types (`availableTypes()`'s
|
||||
/// carve-out). `foreign`'s flat `(type, data)` list cannot represent "the same type on several
|
||||
/// items", which is exactly what a multi-file Finder copy needs seeded.
|
||||
private var seededFileURLs: [URL] = []
|
||||
|
||||
func manifestData() -> Data? { data }
|
||||
|
||||
@discardableResult
|
||||
@@ -53,12 +59,15 @@ final class FakePasteboard: ClipboardPasteboard {
|
||||
return foreign.first { $0.type == type }?.data
|
||||
}
|
||||
|
||||
func fileURLs() -> [URL] { seededFileURLs }
|
||||
|
||||
/// Another app copied: ownership moves, our type is gone, the counter advanced.
|
||||
func takeOver() {
|
||||
changeCount += 1
|
||||
data = nil
|
||||
text = nil
|
||||
foreign = []
|
||||
seededFileURLs = []
|
||||
}
|
||||
|
||||
/// Another app put *these* flavors down — a screenshot, a browser's Copy Image, a Finder copy.
|
||||
@@ -68,6 +77,27 @@ final class FakePasteboard: ClipboardPasteboard {
|
||||
takeOver()
|
||||
foreign = payloads
|
||||
}
|
||||
|
||||
/// A Finder copy of one or more **files** — every URL as its own pasteboard item, exactly as a
|
||||
/// real multi-select copy is, with `public.file-url` reported through `availableTypes()` (the
|
||||
/// first-item read the image branch's precedence classifies) so `carriesFileURL` sees it. `also`
|
||||
/// seeds types riding beside the file URL on that same first item — an image flavor, for the
|
||||
/// precedence tests where a Finder-copied image file carries both.
|
||||
func seedFileURLs(_ urls: [URL], also: [(type: String, data: Data)] = []) {
|
||||
takeOver()
|
||||
seededFileURLs = urls
|
||||
foreign = urls.isEmpty ? also : [(UTType.fileURL.identifier, Data())] + also
|
||||
}
|
||||
|
||||
/// A foreign type layered over whatever this pasteboard already holds, counter bumped but nothing
|
||||
/// cleared — a combination `write()` alone can never produce (a real copy's `clearContents()`
|
||||
/// takes everything with it), but exactly what "the app's own type wins outright" needs to
|
||||
/// construct to prove the guard actually fires rather than merely never being exercised
|
||||
/// (`PastedImageClassificationTests.boardItemsWin`'s same synthetic shape, one layer up).
|
||||
func layerForeign(_ payloads: [(type: String, data: Data)]) {
|
||||
changeCount += 1
|
||||
foreign = payloads
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Fixtures
|
||||
|
||||
Reference in New Issue
Block a user