Implement the hybrid clipboard with deferred cut
⌘X/⌘C/⌘V for cards and lanes per 04-interactions.md § Clipboard: - ClipboardStore stages full folder snapshots eagerly at the gesture into Application Support (at most the current copy; sweep at launch and on each copy purges what the pasteboard no longer references; a copy made before quitting pastes whole after restart) and writes the pasteboard a JSON manifest — every entry embedding its index.md, lane entries their cards' too — plus plain-text titles. - Cut is Finder-style deferred: items dim in place off pendingCut, void on pasteboard takeover (changeCount, no timers), source-board close, or per-item external tombstoning; the first armed paste moves the surviving originals whole (tombstoned interior cards land in the destination's trash), a second paste materializes copies from staging. - Paste anchors by the shared flatten-order rule (NewCardTarget's anchor, extracted); a tombstoned selection never anchors; lane paste reaches the right end and stays enabled on a zero-lane board; paste into the source board is the within-board lane duplicate; copies keep created, take fresh GUIDs, and strip tombstoned cards; trash-sourced copies strip deleted: at materialization; ⌘X is disabled on the trash side. - A degraded paste is loud, never silent: staging gone → the embedded index.md fallback lands content-intact, attachments absent, and a BannerCenter-phrased row names what was lost. - The standard Edit items validate through conditionally-attached onCommand handlers, so AppKit's enablement mirrors the availability predicates; text fields keep their own clipboard while focused. 879 unit tests (68 new). Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -30,6 +30,10 @@ import SwiftUI
|
||||
/// outward, and Select All. The chorded commands are the menu's (`BoardCommands`); everything
|
||||
/// here is a plain key or a grammar modifier, which is exactly the split 04-interactions.md ▸
|
||||
/// Configurable bindings draws between what remaps and what does not.
|
||||
/// - **The standard Edit items the board answers as a responder** — Select All, and Cut/Copy/Paste
|
||||
/// beside it (`ClipboardCommands.swift`). They are not menu items of ours: the Edit menu already
|
||||
/// carries those titles, and titles are the remapping mechanism's key, so a second row sharing one
|
||||
/// is ruled out (04-interactions.md ▸ Configurable bindings).
|
||||
///
|
||||
/// - **The trash quasi-lane** — trailing, one fixed unit, joining and leaving the width division as
|
||||
/// View ▸ Show Trash toggles it (`TrashLaneView`, 03-board-ui.md § Trash).
|
||||
@@ -179,6 +183,11 @@ struct BoardView: View {
|
||||
// forbids outright (04 ▸ Configurable bindings). A focused text field consumes it first, so
|
||||
// ⌘A inside an inline editor stays text selection with no guard needed here.
|
||||
.onCommand(#selector(NSText.selectAll(_:))) { store.selectAll() }
|
||||
// **Cut / Copy / Paste** (04-interactions.md ▸ Clipboard), through the same responder door
|
||||
// Select All above uses and for the same titles-are-API reason. Each handler is attached only
|
||||
// while its command applies, which is what makes AppKit's automatic enablement mirror the
|
||||
// validation exactly — see `boardClipboardCommands`.
|
||||
.boardClipboardCommands(store: store, clipboard: appModel.clipboard)
|
||||
// **Belt** for a session SwiftUI does report ending: immediate cleanup, gated on the physical
|
||||
// mouse button being up. A finished session's phase events can arrive *after* the user has
|
||||
// started the next drag, and an ungated handler would wipe the new session's state — no
|
||||
|
||||
Reference in New Issue
Block a user