Materialize the trash — faces, menus, and grammar

Phase 3 finishes the pivot at the surface. One card face serves two
containers: CardFaceView extracted with a role — board or trash — so
stripe, tint, chip, selection stroke, cut dim, marquee registration,
and drag are shared by construction, the trash side differing only in
its absences: no Open, no rename, no Style, no file-hover highlight,
and a Delete that goes through the confirmation host. The column
rewrote around the lanes' own single-column masonry so drag reflow
reads as positional slides; chrome stays the hatched header, symbol,
and count — 11 gives Empty Trash to the File menu alone. Two real
grammar bugs die here: plain Backspace on a trash selection purged
without the confirmation the menu raises, and the context menu's
Delete resolved against the standing selection, so right-clicking a
trash card under a board selection silently did nothing — it now
stages the clicked set explicitly. Open, Rename, Style, and Empty
Trash validation became testable store seams; the column is one named
accessibility container of ordinary card elements. The tombstone era
is swept: deleteItem, restoreItem, stripTombstonedChildren — dead
since lane copies stopped nesting trash — the restore verb, the
unreachable put-back banner row, and every quasi-lane doc comment.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-28 18:18:39 -04:00
parent 53bc71f7fb
commit 797d020d01
34 changed files with 1272 additions and 1422 deletions
+4 -4
View File
@@ -48,7 +48,7 @@ struct DropTarget: Equatable, Sendable {
// MARK: - Dropping on the trash
/// **Drop-on-trash deletes** (04-interactions.md The trash, settled 2026-07-28: "the drag becomes
/// the pointer's delete gesture release tombstones the dragged card(s), exactly the tombstone"),
/// the pointer's delete gesture release moves the dragged card(s) into `.trash/`, exactly the delete"),
/// as the two pure facts the gesture is made of (`TrashDropTests`).
///
/// Kept out of the drop context so the ruling is checkable without a window, and stated once so the
@@ -70,7 +70,7 @@ enum TrashDrop {
/// refusal 04 states in its own words:
///
/// - **Lanes are not deliverable this way** "a lane drag proposes only lane slots". (The strip's
/// slot list has never contained the quasi-lane, so this is belt over braces; it is written down
/// slot list has never contained the trash column, so this is belt over braces; it is written down
/// because a guard that is only true by construction is one refactor from being false.)
/// - **A trash card is already there.** A `.trash` session's vocabulary is restore and copy-out;
/// dropping it back where it came from writes nothing.
@@ -78,7 +78,7 @@ enum TrashDrop {
/// delivered *into* this board's trash would be a transfer-and-delete compound, an operation the
/// design gives no name and no undo story. The card stays where it is.
/// - ** is refused.** Copying into the trash is not a thing the copy grammar promises the
/// original stays exactly where it was, and there is nothing to tombstone but the original.
/// original stays exactly where it was, and there is nothing to delete but the original.
/// - **Hidden, the trash is invisible to every gesture.** True by construction too (the column is
/// not rendered, so it has no drop region), and stated here so the claim is testable.
/// - **The mutating-gesture rule**, like every other write the pointer can start.
@@ -306,7 +306,7 @@ final class DragSession {
// MARK: Where it would land
/// The current proposal, or `nil` when the drag has none a fresh session before the first
/// sample, or one whose target lane was tombstoned in a reload (rule 2 of the re-grounding
/// sample, or one whose target lane vanished in a reload (rule 2 of the re-grounding
/// trio). **Release with no valid proposal cancels.**
private(set) var proposal: DropTarget?