/// Where ⌘V lands (04-interactions.md ▸ Clipboard), as pure functions of the selection, the /// last-active lane, and the snapshot (`PasteTargetTests`). /// /// The two rules verbatim, and each clause's branch below: /// /// > Paste lands after the anchor card (or appends to a selected lane); a multi-selection anchors at /// > its last member in flatten order — the ⌘N target rule's shared anchor. … **A tombstoned /// > selection never anchors paste**: ⌘V stays enabled and behaves exactly as with nothing selected — /// > a card payload appends to the last-active lane, a lane payload lands at the board's right end. /// /// > **Lane paste** lands after the anchor lane — the selected lane, or the selected card's lane /// > (several selected: the last, per the shared anchor rule); nothing selected = the board's right /// > end. /// /// Plus 04 ▸ The map's zero-lane clause: "New Card, Return-creation, and Paste with a *card* payload /// disable via menu validation until a lane exists … Paste with a **lane** payload … stays enabled /// and lands at the board's right end". /// /// **Pure, for `NewCardTarget`'s reason** — the branches become lines of test rather than gestures to /// drive, and the menu item's `disabled` reads the *same* answer as the paste's own target rather /// than a second, hand-kept-in-sync condition. /// /// ### What it deliberately reuses /// /// The anchor is `NewCardTarget.flattenAnchor`, not a second walk: 04 says creation and paste share /// one anchor ("the ⌘N target rule's shared anchor"), and two derivations of "the last member in /// flatten order" would be two chances for them to disagree. The card branch goes further and reuses /// `NewCardTarget.resolve` whole, because a card paste's *fallback* is the ⌘N rule's fallback too — /// the last-active lane, then the first lane. Only the lane branch stops at the anchor, because its /// fallback is the board's right end instead. enum PasteTarget { /// Where a card payload lands: which lane, and the position among that lane's rendered cards. struct Cards: Equatable { let laneID: ItemID /// A position in the lane's logical card order, counted among what it renders *now* — the /// convention every arrival path here takes (`BoardStore.receiveCards`). let index: Int } /// The card payload's target, or `nil` on a **zero-lane board** — which is therefore the menu /// item's `disabled` condition as well as the paste's refusal, so the two cannot disagree. static func cards( selection: ItemReferenceSet, lastActiveLaneID: ItemID?, snapshot: BoardModel ) -> Cards? { guard let resolution = NewCardTarget.resolve( selection: selection, lastActiveLaneID: lastActiveLaneID, snapshot: snapshot ), let lane = snapshot.lanes.first(where: { $0.id == resolution.laneID && !$0.isDeleted }) else { return nil } let rendered = lane.cards.filter { !$0.isDeleted } // `insertionIndex` answers `nil` for "append", which is `rendered.count` — the same position // said two ways, and the creation path's own degradation for an anchor that has since gone. let index = BoardStore.insertionIndex(after: resolution.anchorCardID, among: rendered) ?? rendered.count return Cards(laneID: lane.id, index: index) } /// The lane payload's slot among the board's live lanes — **always an answer**, zero-lane board /// included, because lane paste "stays enabled and lands at the board's right end" whatever the /// board holds. That is what makes it the other way out of a board with no lanes. static func lanes(selection: ItemReferenceSet, snapshot: BoardModel) -> Int { let lanes = snapshot.lanes.filter { !$0.isDeleted } guard let anchor = NewCardTarget.flattenAnchor(selection: selection, snapshot: snapshot), let position = lanes.firstIndex(where: { $0.id == anchor.laneID }) else { // Nothing selected, a tombstoned selection, or a stale one: the right end. return lanes.count } return position + 1 } }