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:
@@ -128,7 +128,7 @@ struct BoardDropContext {
|
||||
// MARK: Re-grounding
|
||||
|
||||
/// **Rule 2 of the mid-drag re-grounding trio** (04-interactions.md ▸ Drag and drop): a proposal
|
||||
/// whose target lane was tombstoned or vanished in a reload is invalidated — tombstoned lanes are
|
||||
/// whose target lane vanished in a reload is invalidated — deleted lanes are
|
||||
/// never drop targets — the shadow withdraws, and no proposal stands until the pointer reaches a
|
||||
/// live target.
|
||||
///
|
||||
@@ -152,7 +152,7 @@ struct BoardDropContext {
|
||||
/// this strip's standard width — and the cursor is the physical mouse, so neither input is a
|
||||
/// measured frame (03-board-ui.md § Motion).
|
||||
///
|
||||
/// **The terminal slot is clamped before the trash.** The quasi-lane consumes one unit while
|
||||
/// **The terminal slot is clamped before the trash.** The column consumes one unit while
|
||||
/// shown and is never a landing spot for anything (04-interactions.md ▸ The trash: "no move or
|
||||
/// paste ever targets the trash"), so it is absent from the slot list by construction and the end
|
||||
/// slot's uncapped reach past the last real lane lands *before* it.
|
||||
@@ -264,7 +264,7 @@ struct BoardDropContext {
|
||||
/// **A refusal falls through to the strip's own answer rather than withdrawing the proposal**,
|
||||
/// which is precisely what this column did before it had a drop target of its own: a cursor over
|
||||
/// it resolves to no lane, so `retargetCardsFromStrip` holds whatever the shadows already show
|
||||
/// and `retargetLanes` clamps the terminal slot in front of the quasi-lane. That is the
|
||||
/// and `retargetLanes` clamps the terminal slot in front of the trash column. That is the
|
||||
/// hysteresis contract (DRAG-REORDER.md § Hysteresis) and it is also the honest reading of "the
|
||||
/// trash proposes nothing for you": the column declines to be a target, it does not cancel the
|
||||
/// drag the user is still holding. So a lane drag reorders across the column exactly as it always
|
||||
@@ -390,9 +390,9 @@ struct BoardDropContext {
|
||||
/// dispatch) and the live handler for the strip's own surfaces — and unlike a card session, those
|
||||
/// surfaces genuinely clear the proposal rather than holding it. A cursor over a gap, the outer
|
||||
/// margin, or **the trash column** is over no lane at all (`LaneLayoutMath.laneIndex` answers
|
||||
/// `nil` there, since the quasi-lane is absent from the live lane list by construction), so the
|
||||
/// `nil` there, since the trash column is absent from the lane list by construction), so the
|
||||
/// highlight withdraws and a release refuses: "Finder file drops (attachment import) on
|
||||
/// tombstoned cards are inert" and the trash column is never a target (04-interactions.md ▸ The
|
||||
/// trash cards are inert" and the trash column is never a file-drop target (04-interactions.md ▸ The
|
||||
/// trash).
|
||||
func retargetFileFromStrip(_ info: DropInfo) {
|
||||
guard acceptsFileDrop(info), let cursor = stripCursor() else {
|
||||
@@ -490,7 +490,7 @@ struct BoardDropContext {
|
||||
/// against whatever the last render believed:
|
||||
///
|
||||
/// 1. the geometry was re-derived on every sample and the proposal is what it produced;
|
||||
/// 2. a proposal naming a vanished or tombstoned lane is invalidated, and **release with no valid
|
||||
/// 2. a proposal naming a vanished lane is invalidated, and **release with no valid
|
||||
/// proposal cancels** — items return, nothing is written;
|
||||
/// 3. an emptied drag cancels itself, and a partly emptied one drops the survivors.
|
||||
///
|
||||
@@ -531,7 +531,7 @@ struct BoardDropContext {
|
||||
case .lanes:
|
||||
// **A lane drag never targets the trash**, and never a masonry either — a lane session
|
||||
// proposes only lane slots (04-interactions.md ▸ The trash). True by construction, since
|
||||
// `retargetLanes` is the only thing that proposes for one and the quasi-lane is absent
|
||||
// `retargetLanes` is the only thing that proposes for one and the trash column is absent
|
||||
// from its slot list; written down because a commit that trusted the container implicitly
|
||||
// would be the one place the invariant could break silently.
|
||||
guard target.container == .strip else {
|
||||
@@ -758,7 +758,7 @@ struct StripDropDelegate: DropDelegate {
|
||||
/// runs is the strip's `retargetLanes` — lane reordering keeps working across the column exactly as
|
||||
/// it did when the column was a hole in the strip's target;
|
||||
/// - **Finder file sessions** by clearing the highlight outright: "Finder file drops (attachment
|
||||
/// import) on tombstoned cards are inert" (▸ The trash), and the column has nothing else to offer
|
||||
/// import) on trash cards are inert" (▸ The trash), and the column has nothing else to offer
|
||||
/// them — no lane, no card, nothing to attach to.
|
||||
struct TrashDropDelegate: DropDelegate {
|
||||
|
||||
@@ -783,7 +783,7 @@ struct TrashDropDelegate: DropDelegate {
|
||||
context.session.proposeFile(nil)
|
||||
}
|
||||
|
||||
/// A release on the column commits whatever stands — the tombstone when the trash is the
|
||||
/// A release on the column commits whatever stands — the delete when the trash is the
|
||||
/// proposal, and otherwise the proposal the column declined to displace, which is the same
|
||||
/// "the drop lands where the shadows show" promise as anywhere else.
|
||||
func performDrop(info: DropInfo) -> Bool {
|
||||
|
||||
Reference in New Issue
Block a user