import Foundation // MARK: - HistoryPhrase /// The menu phrase an undo step carries — "Move 3 Cards", "Rename Lane", "Restyle Board". /// /// ### Why the vocabulary is 06's and not a new one /// /// 13-native-undo.md ▸ Rules hands the naming straight over: "The 06 vocabulary supplies menu titles /// ('Undo Move 3 Cards'), via NSUndoManager's dynamic retitling — the same naming machinery both /// editions use." So the verbs here are exactly 06-history-undo.md ▸ Commit messages' list — *Add / /// Delete / Move / Rename / Edit / Restyle / Resize / Reorder over cards, lanes, and the board*, plus /// the trash pair's **Restore** — and the plural rule is that section's own plural folding ("Delete /// 12 cards"), which is also 13's coalescing sentence read out loud: "a multi-card move is one step /// with a plural title". /// /// The **"Undo "/"Redo " prefix is never here**: the platform composes and localizes it /// (`BoardUndoManager.undoMenuItemTitle`), and a phrase that spelled it would read "Undo Undo Move /// Card" in the Edit menu — see `HistoryStep.name`. /// /// ### Title case, unlike a commit subject /// /// A commit subject is a sentence ("Move 3 cards to Done"); a menu item is a title, and macOS titles /// its Edit-menu rows. The words are 06's; the casing is the menu's. Nothing else differs — and the /// destination clause a commit subject carries has no place in a title that has to stay short enough /// for a menu row. /// /// Pure, and its own type rather than a `String` built at each call site, because a phrase composed /// in eleven places is a vocabulary that drifts in eleven places. public enum HistoryPhrase { // MARK: Verbs /// 06-history-undo.md ▸ Commit messages' verb list, restricted to the operations 13 makes /// undoable. `Permanently delete`, `Attach`, `Remove` and `Repair` are deliberately absent — /// those are exactly the operations that register no step at all (13 ▸ Rules ▸ what is not /// undoable, ▸ Out of scope). public enum Verb: String, Sendable, CaseIterable { /// A create — File ▸ New Lane, the new-card placeholder's commit, a Finder file drop's cards. case add = "Add" /// A tombstone (⌫, drop-on-trash, the card window's Actions ▸ Delete). case delete = "Delete" /// Put Back and drag-to-restore. case restore = "Restore" /// A drop that changes an item's parent. case move = "Move" /// A drop, a lane drag or ⌥⌘↑/⌥⌘↓ that changes rank among unchanged siblings. case reorder = "Reorder" case rename = "Rename" case restyle = "Restyle" case resize = "Resize" /// An Edit session's body save — one step at the Edit→Preview flip (13 ▸ Rules). case edit = "Edit" } // MARK: Nouns /// What the gesture acted on. `board` is deliberately count-less: there is one board, and /// "Rename 1 Board" is not a phrase anyone writes. public enum Kind: Sendable, Equatable { case card case lane case board var singular: String { switch self { case .card: "Card" case .lane: "Lane" case .board: "Board" } } var plural: String { switch self { case .card: "Cards" case .lane: "Lanes" case .board: "Board" } } } // MARK: Composition /// The phrase for one gesture: `"Move Card"`, `"Move 3 Cards"`, `"Restyle Board"`. /// /// A `count` of one or less folds to the singular — a batch that turned out to name a single item /// is one item, and a zero never reaches here because a gesture that wrote nothing registers /// nothing. public static func name(_ verb: Verb, kind: Kind, count: Int = 1) -> String { guard count > 1, kind != .board else { return "\(verb.rawValue) \(kind.singular)" } return "\(verb.rawValue) \(count) \(kind.plural)" } }