The Paste row says what it is about to do — one card, one lane, or a count of either
Both context menus (`CardFaceView`'s pointer menu and VoiceOver twin, `LaneView`'s own pair) twin a plain "Paste" row over `ClipboardStore.paste(into:)`. It now reads the clipboard's own manifest and titles itself "Paste Card" / "Paste Lane" for a single copied item, "Paste N Cards" / "Paste N Lanes" for several, and stays plain "Paste" for no app payload — a Finder file copy, a screenshot, anything else the paste command still accepts but never decodes to a `ClipboardManifest`. `ClipboardManifest.pasteMenuTitle(for:)` is the one pure function both menus call — `kind` and `entries.count` are the whole of it, since `SelectionKind` is singular by construction (`SelectionGrammar.mixesKinds` refuses a mixed copy), so there is no mixed shape to compose a plural for. `CardFaceView.pasteTitle` rides the exact Observable read `pasteEnabled` already makes (`appModel.clipboard.payload`) — no new subscription on a builder that is not lazy. `LaneView.pasteTitle` reads the same field directly rather than through `canPaste(into:)`, since this view is already unconditionally subscribed to selection/snapshot and there is no reduction to preserve. Edit ▸ Paste on the menu bar stays plain — it is a responder attached to the platform's own Edit menu row (`ClipboardCommands.boardClipboardCommands`), not a `Button` this app titles, and the card's scope is context menus only. Flagged for a follow-up: DESIGN/11-command-nexus.md's Card and Lane rows (lines 110-111) describe Paste generically and could note the dynamic title. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -287,6 +287,48 @@ struct ClipboardManifestTests {
|
||||
)
|
||||
#expect(manifest.plainText == "First\nUntitled")
|
||||
}
|
||||
|
||||
// MARK: - pasteMenuTitle
|
||||
|
||||
private func manifest(kind: SelectionKind, entryCount: Int) -> ClipboardManifest {
|
||||
ClipboardManifest(
|
||||
copyID: "abc",
|
||||
boardRoot: URL(fileURLWithPath: "/tmp/B", isDirectory: true),
|
||||
kind: kind,
|
||||
container: .board,
|
||||
entries: (0..<entryCount).map { entry("item-\($0)") }
|
||||
)
|
||||
}
|
||||
|
||||
@Test("No app payload titles the row plain 'Paste'")
|
||||
func pasteMenuTitleNoPayload() {
|
||||
#expect(ClipboardManifest.pasteMenuTitle(for: nil) == "Paste")
|
||||
}
|
||||
|
||||
@Test("An entryless manifest — unreachable in practice, but defensive — titles the row plain 'Paste'")
|
||||
func pasteMenuTitleEmptyEntries() {
|
||||
#expect(ClipboardManifest.pasteMenuTitle(for: manifest(kind: .card, entryCount: 0)) == "Paste")
|
||||
}
|
||||
|
||||
@Test("One copied card titles the row 'Paste Card'")
|
||||
func pasteMenuTitleSingleCard() {
|
||||
#expect(ClipboardManifest.pasteMenuTitle(for: manifest(kind: .card, entryCount: 1)) == "Paste Card")
|
||||
}
|
||||
|
||||
@Test("Several copied cards title the row 'Paste N Cards'")
|
||||
func pasteMenuTitleMultipleCards() {
|
||||
#expect(ClipboardManifest.pasteMenuTitle(for: manifest(kind: .card, entryCount: 3)) == "Paste 3 Cards")
|
||||
}
|
||||
|
||||
@Test("One copied lane titles the row 'Paste Lane'")
|
||||
func pasteMenuTitleSingleLane() {
|
||||
#expect(ClipboardManifest.pasteMenuTitle(for: manifest(kind: .lane, entryCount: 1)) == "Paste Lane")
|
||||
}
|
||||
|
||||
@Test("Several copied lanes title the row 'Paste N Lanes'")
|
||||
func pasteMenuTitleMultipleLanes() {
|
||||
#expect(ClipboardManifest.pasteMenuTitle(for: manifest(kind: .lane, entryCount: 2)) == "Paste 2 Lanes")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Copy and staging
|
||||
|
||||
Reference in New Issue
Block a user