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:
@@ -68,10 +68,42 @@ enum LaneLayoutMath {
|
||||
///
|
||||
/// The caller decides *which* lanes: the strip passes the live ones in snapshot order, because
|
||||
/// a tombstoned lane renders nowhere on the board (03-board-ui.md § Trash collapses it to a
|
||||
/// single trash entry) and so consumes none of the window's width. When the trash quasi-lane
|
||||
/// arrives it joins this total as one fixed unit — "Show/Hide Trash is a re-divide trigger".
|
||||
static func totalUnits(of lanes: [Lane]) -> Int {
|
||||
max(1, lanes.reduce(0) { $0 + displayUnits(of: $1) })
|
||||
/// single trash entry) and so consumes none of the window's width.
|
||||
///
|
||||
/// **`trashUnits` is the quasi-lane's fixed one unit, and it is *only* consumed while shown**
|
||||
/// (03-board-ui.md § Trash): the trash "spans a fixed one width unit — no `width` frontmatter,
|
||||
/// and neither the stepper nor the edge drag applies — consumed only while shown". Passing it
|
||||
/// here rather than fabricating a `Lane` for the trash is what keeps that true: there is no lane
|
||||
/// value anywhere that a reorder, a resize or a width write could reach.
|
||||
///
|
||||
/// Show/Hide Trash is therefore a **re-divide trigger** and nothing more — the window is
|
||||
/// untouched, and the existing width divides across one more (or one fewer) unit, exactly as a
|
||||
/// lane add does (§ Layout — full visibility).
|
||||
static func totalUnits(of lanes: [Lane], trashUnits: Int = 0) -> Int {
|
||||
max(1, lanes.reduce(0) { $0 + displayUnits(of: $1) } + max(0, trashUnits))
|
||||
}
|
||||
|
||||
// MARK: - Hit testing
|
||||
|
||||
/// Which lane sits under `x` in strip coordinates (0 at the strip's leading edge, outer margin
|
||||
/// included) — an index into `unitCounts`, or `nil` when `x` is not over a lane at all.
|
||||
///
|
||||
/// **The gaps and the margins answer `nil` deliberately**, and so does everything past the last
|
||||
/// lane — which is where the trash quasi-lane sits. That is the whole of drag-to-restore's
|
||||
/// "a drop anywhere else is a no-op" (03-board-ui.md § Trash): a drop that does not land
|
||||
/// squarely on a live lane writes nothing rather than guessing at the nearest one.
|
||||
///
|
||||
/// Same analytic geometry as `LaneReorderMath.proposedIndex` — resting positions computed from
|
||||
/// the unit counts, never measured frames (03-board-ui.md § Motion, "motion never feeds back
|
||||
/// into logic").
|
||||
static func laneIndex(atX x: CGFloat, unitCounts: [Int], standard: CGFloat, gap: CGFloat) -> Int? {
|
||||
var left = gap
|
||||
for (index, units) in unitCounts.enumerated() {
|
||||
let width = slotWidth(units: units, standard: standard, gap: gap)
|
||||
if x >= left, x < left + width { return index }
|
||||
left += width + gap
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - The drag's snap
|
||||
|
||||
Reference in New Issue
Block a user