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:
@@ -161,6 +161,43 @@ public struct ClipboardManifest: Codable, Sendable, Equatable {
|
||||
entries.map { $0.title ?? "Untitled" }.joined(separator: "\n")
|
||||
}
|
||||
|
||||
// MARK: - Paste menu title
|
||||
|
||||
/// **The context-menu Paste row's title** (Pipeline card 36cce96a — "the paste option should show
|
||||
/// 'Paste Card' or 'Paste Lane' or 'Paste N Cards' depending on what's in pasteboard"): "Paste
|
||||
/// Card"/"Paste Lane" for one entry, "Paste N Cards"/"Paste N Lanes" for several, plain "Paste"
|
||||
/// for no app payload at all. One pure function of the manifest, called verbatim by both context
|
||||
/// menus (`CardFaceView.pasteTitle`, `LaneView.pasteTitle`) — a title composed twice is a title
|
||||
/// that drifts twice.
|
||||
///
|
||||
/// **`kind` and `entries.count` are the whole of it** — exactly the two fields the type comment
|
||||
/// already promises are the selection's own vocabulary and its recorded order, so this reads no
|
||||
/// more of the manifest than that. There is no *mixed* shape to compose a plural for: `kind` is
|
||||
/// singular by construction — `SelectionGrammar.mixesKinds` refuses a selection that would produce
|
||||
/// one, so `ClipboardStore.capture` never writes a manifest naming both cards and lanes. Every
|
||||
/// manifest this app ever produces is cards-only or lanes-only, which is what makes the
|
||||
/// card-family/lane-family split exhaustive rather than a case among others.
|
||||
///
|
||||
/// **`nil` is the caller's to pass, not this function's to look up.** Both menus already hold
|
||||
/// `payload` for their own `.disabled` reads (`CardFaceView.pasteEnabled`, `LaneView.pasteEnabled`
|
||||
/// via `canPaste(into:)`), so a caller with no app payload passes `nil` rather than this function
|
||||
/// reaching for a store it has no seam to. Answering plain "Paste" here is also the whole of
|
||||
/// **foreign pasteboard content's** title: a Finder file copy or a screenshot that the paste
|
||||
/// command still accepts (`ClipboardStore.canPasteFiles`/`canPasteImage`) never decodes to a
|
||||
/// `ClipboardManifest` in the first place, so it never reaches any branch but this one.
|
||||
///
|
||||
/// **An empty `entries` answers plain "Paste" too**, though nothing reachable ever produces one:
|
||||
/// `init?(data:)` refuses a decoded manifest with no entries, and `ClipboardStore.capture` refuses
|
||||
/// an empty selection before a manifest is ever built. The clause exists so this function has no
|
||||
/// partial case, not because the branch is live.
|
||||
public static func pasteMenuTitle(for payload: ClipboardManifest?) -> String {
|
||||
guard let payload, !payload.entries.isEmpty else { return "Paste" }
|
||||
let count = payload.entries.count
|
||||
let noun = payload.kind == .card ? "Card" : "Lane"
|
||||
guard count > 1 else { return "Paste \(noun)" }
|
||||
return "Paste \(count) \(noun)s"
|
||||
}
|
||||
|
||||
// MARK: Coding
|
||||
|
||||
public func encoded() -> Data? {
|
||||
|
||||
Reference in New Issue
Block a user