Build the trash quasi-lane

Deletion becomes a two-stage, Finder-style story. File > Delete and
plain Backspace tombstone the live selection; View > Show Trash (no
chord — Shift-Cmd-T stays with the system's tab bar) reveals the
quasi-lane: trailing, one fixed width unit consumed only while shown,
hatched dimmed header, count badge, no new-card button, exempt from
resize and reorder alike. Its contents are a pure view over the
snapshot — the deterministic sort (deleted newest first, folder-name
ties, unparseable stamps oldest) interleaves card rows with a
tombstoned lane's single entry, whose count names what Put Back
returns; the ancestor walk is absolute, so an own-flag card beneath a
tombstoned lane has no row and recovery is deliberately two steps.
Put Back twins Delete on Cmd-Backspace with validation enabling
exactly one; restore fidelity is byte-perfect because nothing ever
moved. Delete Immediately confirms exactly where loss is real (every
board is mode-none today; the predicate names the git carve-out for
m7), Empty Trash always confirms with the true whole-board count,
and dragging a tombstoned card onto a live lane restores it there —
positional drops and cross-board locality arrive with m5's machinery.
The banner's delete phrasing drops "move to the trash" per the naming
constraint: board deletion says Delete, "Move to Trash" stays
reserved for the system Trash. 47 new tests.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-27 15:38:34 -04:00
parent aa6aaf2a11
commit 2090d742b9
14 changed files with 2428 additions and 18 deletions
+8 -4
View File
@@ -422,9 +422,13 @@ public final class BannerCenter {
///
/// Titles are quoted where the operation carries one and the phrasing stays graceful where it
/// 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 are
/// Finder's, matching the commands the user pressed (03-board-ui.md § Trash): Move to Trash,
/// Put Back, Delete Immediately.
/// "the item" rather than guessing "card" and being wrong about a lane.
///
/// The trash verbs match the commands the user pressed **Delete**, Put Back, Delete
/// Immediately 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:
@@ -440,7 +444,7 @@ public final class BannerCenter {
case let .copy(title):
if let title { "Couldn't copy '\(title)'" } else { "Couldn't copy the item" }
case let .delete(title):
if let title { "Couldn't move '\(title)' to the trash" } else { "Couldn't move the item to the trash" }
if let title { "Couldn't delete '\(title)'" } else { "Couldn't delete the item" }
case let .restore(title):
if let title { "Couldn't put '\(title)' back" } else { "Couldn't put the item back" }
case let .purge(title):