Realign code with the 2026-07-31 rulings
The trash sorts by modified descending — the arrival rank mint retires (Ranks.isOrderedForTrash one comparator, loader + merged order agree; the legacy deleted: migration stamps modified from the tombstone timestamp where parseable; delete undo steps validate existence-only; agent guide v8). Trash selection goes kind-blind — ranges, marquee, Select All, and the successor walk sweep both kinds; the guard moves to the exits (mixed-payload drop refusal, copy/cut validation). The copy stamping preflight widens back to comment depth (load-scoped posture — the board always loads, the gesture refuses whole). Fixes a latent no-op: trashed-lane drag restore never fired (DragSession.beginLanes hard-coded the board container). 2403 tests in 413 suites green. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -76,12 +76,12 @@ extension ItemContainer {
|
||||
// MARK: - The trash's two kinds, in one order
|
||||
|
||||
/// One row of the trash column — a card, or a trashed lane's opaque unit (03-board-ui.md § Trash,
|
||||
/// re-ruled 2026-07-29: "Lane rows and cards interleave in the one trash column purely by trash
|
||||
/// rank").
|
||||
/// re-ruled 2026-07-31: "Lane rows and cards interleave in the one trash column by `modified`
|
||||
/// descending").
|
||||
///
|
||||
/// **It exists so the interleave is written once.** The container's two kinds are two arrays on the
|
||||
/// snapshot, for the reason `BoardModel.trash` states — a trashed card is an ordinary card every
|
||||
/// card-shaped surface already reads, a trashed lane is an opaque row none of them may — but *rank
|
||||
/// card-shaped surface already reads, a trashed lane is an opaque row none of them may — but *column
|
||||
/// order* is a question about the container as a whole, and it is asked by the column that draws the
|
||||
/// rows, by the grammar that ranges and navigates over them, and by the path resolver that batches
|
||||
/// them. Three merges would be three chances to disagree about what "the row below this one" is.
|
||||
@@ -100,8 +100,28 @@ public enum TrashEntry: Identifiable, Sendable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
/// The rank that decides where this row sits among the others — the one field both kinds carry
|
||||
/// for the same purpose.
|
||||
/// **The stamp that decides where this row sits among the others** — the trash sorts by
|
||||
/// `modified` descending (01-storage-format.md § Deletion, re-ruled 2026-07-31), and the trash
|
||||
/// move is what writes it. `nil` for an entry whose mover skipped the restamp, which sorts below
|
||||
/// every dated sibling (`Ranks.isOrderedForTrash`).
|
||||
public var modified: Date? {
|
||||
switch self {
|
||||
case let .card(card): card.modified.value
|
||||
case let .lane(lane): lane.modified.value
|
||||
}
|
||||
}
|
||||
|
||||
/// The first tie-break's key — the title as the row draws it, `nil` when the entry has none.
|
||||
public var title: String? {
|
||||
switch self {
|
||||
case let .card(card): card.title.value
|
||||
case let .lane(lane): lane.title.value
|
||||
}
|
||||
}
|
||||
|
||||
/// The rank the entry is *carrying*, untouched by the trash move — its position among the lane
|
||||
/// (or board) siblings it left, which a restore returns it to. Deliberately **not** what orders
|
||||
/// this row: the trash is sorted by `modified` (above).
|
||||
public var order: Double {
|
||||
switch self {
|
||||
case let .card(card): card.order
|
||||
@@ -131,15 +151,17 @@ public enum TrashEntry: Identifiable, Sendable, Equatable {
|
||||
extension BoardModel {
|
||||
|
||||
/// The trash's rows, top to bottom — **the container's one order**, both kinds interleaved by
|
||||
/// rank (03-board-ui.md § Trash).
|
||||
/// `modified` descending (03-board-ui.md § Trash, re-ruled 2026-07-31: "The merged order is one
|
||||
/// derivation … a second implementation of 'the row below this one' is a bug by definition").
|
||||
///
|
||||
/// The tie-break is the folder name's, `Ranks.sortedForDisplay`'s own, which is what the loader
|
||||
/// already applied within each kind: two rows minted the same rank by two writers order the same
|
||||
/// way twice.
|
||||
/// The tail is `Ranks.isOrderedForTrash`'s — title case-insensitively, then folder name — which
|
||||
/// is what the loader already applied within each kind, so the merge of two sorted arrays and
|
||||
/// each array alone agree everywhere they overlap.
|
||||
public var trashEntries: [TrashEntry] {
|
||||
Ranks.sortedForDisplay(
|
||||
Ranks.sortedForTrash(
|
||||
trash.map(TrashEntry.card) + trashedLanes.map(TrashEntry.lane),
|
||||
order: \.order,
|
||||
modified: \.modified,
|
||||
title: \.title,
|
||||
name: { $0.id.rawValue }
|
||||
)
|
||||
}
|
||||
@@ -230,10 +252,10 @@ extension ItemPath {
|
||||
/// fails partway must fail the same way twice (`BoardStore.styleSubjects` makes the same choice
|
||||
/// for the same reason).
|
||||
///
|
||||
/// **The trash's order interleaves its two kinds by rank** (03-board-ui.md § Trash: "lane rows
|
||||
/// and cards interleave in the one trash column purely by trash rank"), which is why the walk is
|
||||
/// `trashEntries` rather than the two arrays concatenated: the column's order is the batch's
|
||||
/// order, and it is stated in exactly one place.
|
||||
/// **The trash's order interleaves its two kinds by `modified`** (03-board-ui.md § Trash: "lane
|
||||
/// rows and cards interleave in the one trash column by `modified` descending"), which is why the
|
||||
/// walk is `trashEntries` rather than the two arrays concatenated: the column's order is the
|
||||
/// batch's order, and it is stated in exactly one place.
|
||||
///
|
||||
/// Ids the container does not hold are simply absent, which is every caller's standing posture:
|
||||
/// a selection the next reload will drop writes nothing rather than being refused.
|
||||
|
||||
Reference in New Issue
Block a user