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:
@@ -48,9 +48,9 @@ import os
|
||||
///
|
||||
/// `NSPasteboard.changeCount` is a machine-wide counter, so a value that moved without this store
|
||||
/// moving it means another app owns the pasteboard now. It is checked exactly where 04 says — menu
|
||||
/// validation, app activation, and before every paste — and nowhere else. `payload` is observable
|
||||
/// state rather than a computed pasteboard read precisely so the menu items' enablement re-evaluates
|
||||
/// when it changes rather than whenever SwiftUI happens to rebuild them.
|
||||
/// validation, app activation, before every paste, and a window becoming key — and nowhere else.
|
||||
/// `payload` is observable state rather than a computed pasteboard read precisely so the menu items'
|
||||
/// enablement re-evaluates when it changes rather than whenever SwiftUI happens to rebuild them.
|
||||
///
|
||||
/// **"Menu validation" is two checkpoints here, not a hook**: the commands validate by conditional
|
||||
/// responder attachment (`ClipboardCommands` — availability *is* the handler's presence), which
|
||||
@@ -62,6 +62,15 @@ import os
|
||||
/// screenshot hotkey, ⌃⇧⌘4 — would leave ⌘V dead until the next app switch, which is the image
|
||||
/// branch's headline gesture failing in the exact case it was built for. Both checkpoints are one
|
||||
/// `changeCount` read in the common case, which is why they can afford to fire on every ⌘-chord.
|
||||
///
|
||||
/// **A window becoming key is the fourth checkpoint** (widened 2026-08-09 for the card window's
|
||||
/// paste-image affordance, `CardPasteImageAffordance`): a visible control, unlike a menu item, has to
|
||||
/// read true continuously while its window is frontmost rather than only at the instant something
|
||||
/// probes it. Switching between two already-open windows of this app — no menu opened, no ⌘ pressed
|
||||
/// — crosses neither of the first two checkpoints and stays inside one app activation, so the third
|
||||
/// checkpoint misses it too; `NSWindow.didBecomeKeyNotification` is the moment that is left. Fired
|
||||
/// for every window rather than filtered to card windows, for the same cost reason: one more
|
||||
/// `changeCount` read on an already-cheap path.
|
||||
@MainActor
|
||||
@Observable
|
||||
public final class ClipboardStore {
|
||||
@@ -196,6 +205,22 @@ public final class ClipboardStore {
|
||||
) { [weak self] _ in
|
||||
MainActor.assumeIsolated { self?.refresh() }
|
||||
})
|
||||
// **The fourth checkpoint** (type comment ▸ takeover, widened 2026-08-09 for the card
|
||||
// window's paste-image affordance): any window becoming key. The first three checkpoints
|
||||
// exist for *menu* validation, which only ever runs when something is about to fire — but a
|
||||
// visible control has to read true continuously while its window is frontmost, not only at
|
||||
// the moment a chord or a menu happens to probe it. Switching between two already-open
|
||||
// windows of this app crosses neither of those moments and stays inside one activation, so it
|
||||
// needed a trigger of its own. Every window, not just card windows: cheap (one `changeCount`
|
||||
// read in the common case, `refresh()`'s own guarantee) and simpler than teaching this store
|
||||
// which window kind is asking.
|
||||
stalenessObservers.append(NotificationCenter.default.addObserver(
|
||||
forName: NSWindow.didBecomeKeyNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
MainActor.assumeIsolated { self?.refresh() }
|
||||
})
|
||||
commandKeyToken = NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) { [weak self] event in
|
||||
// Local monitors run on the main thread, before the event reaches its window
|
||||
// (`LocalModifierFlipWatch`'s note) — and the event is returned unchanged, always, for
|
||||
|
||||
Reference in New Issue
Block a user