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:
@@ -143,6 +143,66 @@ enum Ranks: Sendable {
|
||||
items.sorted { isOrderedForDisplay($0, before: $1, order: order, name: name) }
|
||||
}
|
||||
|
||||
// MARK: - The trash's own order
|
||||
|
||||
/// The trash's order: **`modified` descending**, ties broken by title
|
||||
/// (case-insensitive), then folder name (01-storage-format.md § Deletion,
|
||||
/// re-ruled 2026-07-31, retiring the arrival rank mint; 03-board-ui.md §
|
||||
/// Trash: "the trash sorts by `modified` descending … Ties break by title
|
||||
/// (case-insensitive), then folder name — the deterministic tail").
|
||||
///
|
||||
/// **`order` is not consulted at all in here**, which is the whole of the
|
||||
/// ruling: a delete is a pure folder move plus the stamp, and the item's
|
||||
/// `order` key rides along untouched for its eventual restore. The trash is
|
||||
/// the one container in the app whose sequence is not a rank sequence.
|
||||
///
|
||||
/// **An undated entry sorts after every dated one** — the comment thread's
|
||||
/// own rule for a missing `created` ("ties and missing/malformed … sort
|
||||
/// after dated siblings"), read one container over: a stamp the app always
|
||||
/// writes is missing only on a hand-made or foreign entry, and a missing
|
||||
/// value must never outrank a real deletion. The tail then separates them
|
||||
/// exactly as it separates two equal stamps.
|
||||
///
|
||||
/// Titles compare with `localizedCaseInsensitiveCompare`, the same
|
||||
/// comparison the rest of the app's user-facing sorting uses, and an
|
||||
/// untitled entry compares as the empty string — it sorts first among its
|
||||
/// stamp-mates, which is deterministic and is all the tail owes.
|
||||
static func isOrderedForTrash<T>(
|
||||
_ lhs: T, before rhs: T,
|
||||
modified: (T) -> Date?, title: (T) -> String?, name: (T) -> String
|
||||
) -> Bool {
|
||||
let lhsModified = modified(lhs)
|
||||
let rhsModified = modified(rhs)
|
||||
if lhsModified != rhsModified {
|
||||
// Newest first; an absent stamp is older than every present one.
|
||||
switch (lhsModified, rhsModified) {
|
||||
case let (.some(left), .some(right)): return left > right
|
||||
case (.some, .none): return true
|
||||
case (.none, .some): return false
|
||||
case (.none, .none): break
|
||||
}
|
||||
}
|
||||
let lhsTitle = title(lhs) ?? ""
|
||||
let rhsTitle = title(rhs) ?? ""
|
||||
let comparison = lhsTitle.localizedCaseInsensitiveCompare(rhsTitle)
|
||||
guard comparison == .orderedSame else { return comparison == .orderedAscending }
|
||||
return name(lhs) < name(rhs)
|
||||
}
|
||||
|
||||
/// Sorts the trash's entries into column order — `isOrderedForTrash`'s rule,
|
||||
/// applied by the loader to each kind's array and by `BoardModel.trashEntries`
|
||||
/// to the merged sequence, so the two can never disagree about what "the row
|
||||
/// below this one" is (03-board-ui.md § Trash: "The merged order is one
|
||||
/// derivation").
|
||||
static func sortedForTrash<T>(
|
||||
_ items: [T],
|
||||
modified: (T) -> Date?,
|
||||
title: (T) -> String?,
|
||||
name: (T) -> String
|
||||
) -> [T] {
|
||||
items.sorted { isOrderedForTrash($0, before: $1, modified: modified, title: title, name: name) }
|
||||
}
|
||||
|
||||
// MARK: - Tombstone exclusion
|
||||
|
||||
/// `append(toVisible:)`, ignoring tombstoned siblings. Tombstones are
|
||||
|
||||
Reference in New Issue
Block a user