The card window's ⌘V drops its picture branch — the attachments header now offers one instead

Raw image data on a card window's ⌘V was a keyboard shortcut with no visible
trigger; the sidebar's Attachments header now grows a quiet control — beside
the existing add affordance, present only while the pasteboard holds a
picture this card could take — that pastes it through the exact seam the
retired branch used (ClipboardStore.pasteImage(intoCard:in:), the board's
"Paste Image into Card" row's own call). The file-URL branch stays on ⌘V; a
Finder copy is still unambiguous. CardBodyTextView's paste-yield mechanism
needed no change at all — it forwards by capability, not by picture-specific
logic, so a screenshot ⌘V with the body editor focused is now a genuine
no-op there, served by the new control instead.

The pasteboard's re-read gains a fourth checkpoint — a window becoming key —
alongside menu-tracking, ⌘-down and app activation: a persistent visible
control has to read true continuously while its window is frontmost, not
only at the instant a menu or chord probes it.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-09 10:37:55 -04:00
parent 546fd2840f
commit 200fbce276
8 changed files with 343 additions and 49 deletions
+32 -1
View File
@@ -80,7 +80,14 @@ struct CardAttachmentsSection: View {
var body: some View {
VStack(alignment: .leading, spacing: CardWindowMetrics.attachmentRowPadding(bodyPointSize: pointSize)) {
CardSidebarSectionHeader(title: "Attachments") { addAffordance }
CardSidebarSectionHeader(title: "Attachments") {
HStack(spacing: 8) {
if attachments.canPasteImage?() == true {
pasteImageAffordance
}
addAffordance
}
}
if names.isEmpty {
emptyHint
@@ -96,6 +103,30 @@ struct CardAttachmentsSection: View {
// MARK: - Header
/// **The paste-image affordance** the V image-data branch's replacement (04-interactions.md
/// Clipboard, re-ruled 2026-08-09): present only while the pasteboard holds a picture this card
/// could take, rather than a permanent row that would spend most of its life disabled. Its
/// visibility and its tap read and call the exact same seam, `CardAttachments.canPasteImage`/
/// `.pasteImage` the header's own `CardPasteImageAffordance.isVisible` so the button can never
/// promise a paste it does not deliver.
///
/// **Present-or-absent, the hero rows' own posture** (below): a disabled picture-paste icon
/// sitting in this header the other 99% of the time a card window is open would be furniture
/// nobody reads, where "it showed up" is the whole point of the control replacing a keyboard
/// shortcut nobody could see.
private var pasteImageAffordance: some View {
Button {
attachments.pasteImage?()
} label: {
Image(systemName: "photo.badge.plus")
.font(.caption.weight(.semibold))
.foregroundStyle(.secondary)
}
.buttonStyle(.plain)
.help("Paste the copied picture as an attachment")
.accessibilityLabel("Paste Image as Attachment")
}
/// The header's **quiet add affordance** "a pointer twin of File Add Attachment, no
/// separate behavior" (11-command-nexus.md), which is why it calls the same method the menu row
/// does rather than opening a panel of its own.