Realign code with the 2026-07-30 findings-resolution rulings
Delete Immediately is removed entirely (rulingae1dd96, Redesign card d40bfac1): the delete vocabulary is purely staged — board → trash, trash → permanent (confirmed on no-git boards), Empty Trash for bulk. Gone: File ▸ Delete Immediately (⌥⌘⌫) and its validation, both ⌥-alternate context rows (card + the permanently-disabled lane row), the VO custom action, BoardStore.deleteImmediately, TrashModel.canDeleteImmediately, TrashConfirmations' .purge action (zero surviving callers — trash-side Delete always used .deleteTrashCards), and the pinning tests. purgePrompt drops its now-single-purpose container parameter (.trash is the only surviving caller). BoardWriter.purgeItem survives — create-undo rollback still needs it — with its comment rewritten. README's trash paragraph drops the ⌥⌘⌫ sentence. The other 2026-07-30 rulings (2ec2c95registry freshness stamp,c741b02unified-log-as-coerce-consumer) required no code changes — already conformant. Both schemes 1844 tests / 318 suites green. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -732,10 +732,11 @@ public final class BannerCenter {
|
||||
/// does not: the enum knows an item's title, never its *kind*, so an untitled failure says
|
||||
/// "the item" rather than guessing "card" and being wrong about a lane.
|
||||
///
|
||||
/// The trash verbs match the commands the user pressed — **Delete**, Delete Immediately, Empty
|
||||
/// Trash — which is 03-board-ui.md § Trash's naming constraint, settled with the trash UI copy: "Finder's 'Move to Trash' phrasing is reserved for the system Trash; board deletion says
|
||||
/// 'Delete'". A banner saying a card could not be *moved to the trash* would name the wrong one
|
||||
/// of the app's two trashes (the card window's attachment Remove is the other).
|
||||
/// The trash verbs match the commands the user pressed — **Delete**, Empty Trash — which is
|
||||
/// 03-board-ui.md § Trash's naming constraint, settled with the trash UI copy: "Finder's 'Move to
|
||||
/// Trash' phrasing is reserved for the system Trash; board deletion says 'Delete'". A banner
|
||||
/// saying a card could not be *moved to the trash* would name the wrong one of the app's two
|
||||
/// trashes (the card window's attachment Remove is the other).
|
||||
private nonisolated static func actionPhrase(for operation: WriteOperation) -> String {
|
||||
switch operation {
|
||||
case .createBoard:
|
||||
|
||||
@@ -3392,7 +3392,7 @@ public final class BoardStore: HealHost {
|
||||
///
|
||||
/// Its own method rather than a flag on `delete(_:)` because it is a different act with a
|
||||
/// different safety story: it registers **no undo step**, and `purgeIsUnrecoverable` stays `true`
|
||||
/// — 13-native-undo.md ▸ Rules settles this by name ("Permanently delete (Delete Immediately,
|
||||
/// — 13-native-undo.md ▸ Rules settles this by name ("Permanently delete (the trash's Delete,
|
||||
/// Empty Trash) … the confirm *is* the safety"). A stack entry here would be a promise the
|
||||
/// filesystem cannot keep.
|
||||
///
|
||||
@@ -3427,49 +3427,6 @@ public final class BoardStore: HealHost {
|
||||
}
|
||||
}
|
||||
|
||||
/// **Delete Immediately ⌥⌘⌫ — skips the trash from anywhere** (03-board-ui.md § Trash;
|
||||
/// 11-command-nexus.md: "Board window, card selection — skips the trash from anywhere").
|
||||
///
|
||||
/// The one method whose targets can be in either container, and the reason is the command's own
|
||||
/// wording: from a lane it bypasses the trash the ordinary delete would have used, and from the
|
||||
/// trash it is the permanent delete the card is already one keystroke from. Cards only — a lane's
|
||||
/// delete is physical already, so there is nothing for "skip the trash" to mean on one.
|
||||
///
|
||||
/// **It registers no undo step**, `deleteTrashCards`' ruling and its wording.
|
||||
///
|
||||
/// The selection is cleared rather than walked to a successor: unlike ⌫, this is the command a
|
||||
/// confirmation stands in front of, and what follows it is reading the board rather than pressing
|
||||
/// the key again.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - ids: the items to purge — the caller's explicit set, not this store's selection.
|
||||
/// - container: **which side those ids live on**, supplied rather than read off the selection
|
||||
/// (added with the context menu's ⌥-alternate — 11-command-nexus.md ▸ Context menus). The
|
||||
/// menu-bar caller passes the selection's own container and behaves exactly as before; a
|
||||
/// context-menu caller passes `.board`, because "the click names its target" and the target can
|
||||
/// legitimately sit on the other side of the container boundary from a standing selection
|
||||
/// (797d020's explicit-set resolution, whose whole point is that the two can disagree). Reading
|
||||
/// it off the selection here would silently resolve a board card against `.trash` and purge
|
||||
/// nothing — a confirmed destructive command turning into a no-op, which is the one outcome a
|
||||
/// confirmation must never lead to.
|
||||
public func deleteImmediately(_ ids: Set<ItemID>, in container: ItemContainer) {
|
||||
let paths = ItemPath.resolve(ids, in: container, snapshot: snapshot).filter { !$0.isLane }
|
||||
guard !paths.isEmpty else { return }
|
||||
let root = rootURL
|
||||
|
||||
try? performWrite { () throws(BoardWriteError) -> Void in
|
||||
for path in paths {
|
||||
switch path {
|
||||
case .trashCard:
|
||||
try BoardWriter.purgeTrashCard(at: path.folder(under: root), inBoard: root)
|
||||
default:
|
||||
try BoardWriter.purgeItem(at: path.folder(under: root))
|
||||
}
|
||||
}
|
||||
}
|
||||
clearSelection()
|
||||
}
|
||||
|
||||
/// **Empty Trash… ⇧⌘⌫** — purges every card in `<root>/.trash/`, in one bracket.
|
||||
///
|
||||
/// Not undoable, `deleteTrashCards`' ruling — this is the other half of 13's "Permanently delete".
|
||||
|
||||
@@ -41,25 +41,19 @@ public enum TrashModel {
|
||||
public let confirmTitle: String
|
||||
}
|
||||
|
||||
/// The alert in front of a **permanent** card delete — the trash's own ⌫/⌘⌫ and File ▸ Delete
|
||||
/// Immediately alike (03-board-ui.md § Trash: "Both confirm exactly where the loss is real ...
|
||||
/// the alert stands between one keystroke and unrecoverable deletion").
|
||||
/// The alert in front of a **permanent** card delete — the trash's own ⌫/⌘⌫ (03-board-ui.md §
|
||||
/// Trash: "confirms exactly where the loss is real: the alert stands between one keystroke and
|
||||
/// unrecoverable deletion").
|
||||
///
|
||||
/// `container` is where the command found the cards: the trash for the trash's Delete, the board
|
||||
/// for a Delete Immediately that skips the trash from a lane. The prompt reads the same either
|
||||
/// way — what is being asked is whether to destroy these cards, and where they happen to be
|
||||
/// sitting is not the question.
|
||||
///
|
||||
/// A sole card is **named**; several fold into a count. `nil` when the ids name nothing in that
|
||||
/// container, which is also the command's own refusal — so the prompt and the action can never
|
||||
/// A sole card is **named**; several fold into a count. `nil` when the ids name nothing in the
|
||||
/// trash, which is also the command's own refusal — so the prompt and the action can never
|
||||
/// disagree about whether there is anything to purge.
|
||||
public static func purgePrompt(
|
||||
for ids: Set<ItemID>,
|
||||
in container: ItemContainer,
|
||||
snapshot: BoardModel,
|
||||
unrecoverable: Bool
|
||||
) -> PurgePrompt? {
|
||||
let targets = ItemPath.resolve(ids, in: container, snapshot: snapshot).filter { !$0.isLane }
|
||||
let targets = ItemPath.resolve(ids, in: .trash, snapshot: snapshot).filter { !$0.isLane }
|
||||
guard !targets.isEmpty else { return nil }
|
||||
|
||||
let subject: String
|
||||
@@ -98,7 +92,7 @@ public enum TrashModel {
|
||||
/// undo as its net).
|
||||
private static func message(unrecoverable: Bool) -> String {
|
||||
// m7-git: on a git board the content stays reachable in history, so the second sentence is
|
||||
// the honest one — and Delete Immediately does not confirm there at all
|
||||
// the honest one — and the trash's own Delete does not confirm there at all
|
||||
// (`BoardStore.purgeIsUnrecoverable`).
|
||||
unrecoverable
|
||||
? "This can\u{2019}t be undone."
|
||||
@@ -135,13 +129,4 @@ public enum TrashModel {
|
||||
public static func canDelete(selection: ItemReferenceSet, in snapshot: BoardModel) -> Bool {
|
||||
!ItemPath.resolve(selection.ids, in: selection.container, snapshot: snapshot).isEmpty
|
||||
}
|
||||
|
||||
/// Whether File ▸ Delete Immediately has something to purge — **a card selection, from anywhere**
|
||||
/// (11-command-nexus.md: "Board window, card selection — skips the trash from anywhere").
|
||||
///
|
||||
/// Cards only, in either container: a lane's delete is physical already and has undo as its net,
|
||||
/// so there is nothing for "skip the trash" to mean on one.
|
||||
public static func canDeleteImmediately(selection: ItemReferenceSet, in snapshot: BoardModel) -> Bool {
|
||||
ItemPath.resolve(selection.ids, in: selection.container, snapshot: snapshot).contains { !$0.isLane }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user