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:
@@ -375,6 +375,24 @@ struct BannerCenterPhrasingTests {
|
||||
!= BannerCenter.headline(for: error(.style(title: "Fix login"))))
|
||||
}
|
||||
|
||||
@Test("The trash trio speaks the board's vocabulary, never the system Trash's")
|
||||
func trashVerbsFollowTheNamingConstraint() {
|
||||
// 03-board-ui.md § Trash's naming constraint, settled with the trash UI copy: two "Trash"
|
||||
// concepts coexist, and "Finder's 'Move to Trash' phrasing is reserved for the system Trash;
|
||||
// board deletion says 'Delete'". A banner is UI copy like any other.
|
||||
#expect(BannerCenter.headline(for: error(.delete(title: "Fix login"), .io(message: "the disk is full")))
|
||||
== "Couldn't delete 'Fix login' — the disk is full")
|
||||
#expect(BannerCenter.headline(for: error(.restore(title: "Fix login"), .io(message: "the disk is full")))
|
||||
== "Couldn't put 'Fix login' back — the disk is full")
|
||||
#expect(BannerCenter.headline(for: error(.purge(title: "Fix login"), .io(message: "the disk is full")))
|
||||
== "Couldn't permanently delete 'Fix login' — the disk is full")
|
||||
|
||||
for operation in [WriteOperation.delete(title: "Fix login"), .delete(title: nil)] {
|
||||
#expect(!BannerCenter.headline(for: error(operation)).contains("Trash"))
|
||||
#expect(!BannerCenter.headline(for: error(operation)).contains("trash"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test("The cause tail comes from the error's reason and nowhere else")
|
||||
func causeTailCarriesTheDiagnosis() {
|
||||
#expect(BannerCenter.headline(for: error(.move(title: "Fix login"), .io(message: "the disk is full")))
|
||||
|
||||
Reference in New Issue
Block a user