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:
@@ -257,7 +257,13 @@ private struct TrashEntryRow: View {
|
||||
rowFace
|
||||
// The row being dragged out dims in place — the source stays visible in the trash,
|
||||
// because a restore is not a removal until the write lands.
|
||||
.opacity(drops.session.isDragging(entry.id) ? 0.45 : 1)
|
||||
.opacity(drops.session.isDragging(entry.id) ? ClipboardTreatment.dimmedOpacity : 1)
|
||||
// The deferred cut wears the same dim wherever it lands, so the treatment is stated for
|
||||
// every surface a `pendingCut` could name rather than for two of the three. In practice
|
||||
// it never fires here: ⌘X is disabled on tombstoned selections (04-interactions.md ▸ The
|
||||
// trash), and a pending cut is homogeneous by liveness — a reload that tombstones a cut
|
||||
// card *ejects* it from the set rather than moving it to the other side.
|
||||
.cutTreatment(of: entry.id, in: store)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { select() }
|
||||
.marqueeTarget(entry.id, kind: entry.isLaneEntry ? .lane : .card, side: .trashed, in: registry)
|
||||
|
||||
Reference in New Issue
Block a user