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:
@@ -317,8 +317,10 @@ public enum BoardLoader: Sendable {
|
||||
var trash: [Card] = []
|
||||
var trashedLanes: [TrashedLane] = []
|
||||
/// Every trash entry as the dedupe needs it, kind-blind — the container is one flat list to
|
||||
/// the identity rule, whatever the snapshot splits it into.
|
||||
var trashEntries: [(id: ItemID, title: String?, order: Double)] = []
|
||||
/// the identity rule, whatever the snapshot splits it into. Carried with the two keys the
|
||||
/// container's order is stated in (`Ranks.isOrderedForTrash`), never `order`: the trash is
|
||||
/// sorted by `modified` descending since 2026-07-31.
|
||||
var trashEntries: [(id: ItemID, title: String?, modified: Date?)] = []
|
||||
var trashKinds: [ItemID: IntegrityRules.ObjectKind] = [:]
|
||||
for entryURL in trashCandidates(in: boardRoot) {
|
||||
let entryName = entryURL.lastPathComponent
|
||||
@@ -360,7 +362,7 @@ public enum BoardLoader: Sendable {
|
||||
)
|
||||
let id = ItemID(rawValue: entryName)
|
||||
trashKinds[id] = kind
|
||||
trashEntries.append((id: id, title: document.title.value, order: order))
|
||||
trashEntries.append((id: id, title: document.title.value, modified: document.modified.value))
|
||||
|
||||
switch kind {
|
||||
case .lane:
|
||||
@@ -372,6 +374,7 @@ public enum BoardLoader: Sendable {
|
||||
id: id,
|
||||
schema: schema,
|
||||
title: document.title,
|
||||
modified: document.modified,
|
||||
order: order,
|
||||
heldCards: children().count,
|
||||
document: document
|
||||
@@ -407,12 +410,19 @@ public enum BoardLoader: Sendable {
|
||||
// Display order is settled here rather than in the `BoardModel` call below, because the
|
||||
// dedupe's last tie-break *is* traversal order and the rule needs the occurrences in it.
|
||||
let orderedLanes = Ranks.sortedForDisplay(walkedLanes, order: \.order, name: \.name)
|
||||
let orderedTrash = Ranks.sortedForDisplay(trash, order: \.order, name: { $0.id.rawValue })
|
||||
let orderedTrashedLanes = Ranks.sortedForDisplay(trashedLanes, order: \.order, name: { $0.id.rawValue })
|
||||
// The trash's own display order, **both kinds at once** — the column interleaves them by rank
|
||||
// **The trash's arrays are sorted by `modified` descending**, not by `order` (01-storage-format.md
|
||||
// § Deletion, re-ruled 2026-07-31): the trash move rewrites no rank, so the stamp *is* the
|
||||
// position. Each kind is sorted by the same comparator the merged `BoardModel.trashEntries`
|
||||
// applies, which is what makes a kind-narrowed slice of the column agree with the column.
|
||||
let orderedTrash = Ranks.sortedForTrash(
|
||||
trash, modified: { $0.modified.value }, title: { $0.title.value }, name: { $0.id.rawValue })
|
||||
let orderedTrashedLanes = Ranks.sortedForTrash(
|
||||
trashedLanes, modified: { $0.modified.value }, title: { $0.title.value }, name: { $0.id.rawValue })
|
||||
// The trash's own display order, **both kinds at once** — the column interleaves them
|
||||
// (03-board-ui.md § Trash), and the dedupe's last tie-break is stated in traversal order, so
|
||||
// the two kinds are merged before the rule sees them rather than after.
|
||||
let orderedTrashEntries = Ranks.sortedForDisplay(trashEntries, order: \.order, name: { $0.id.rawValue })
|
||||
let orderedTrashEntries = Ranks.sortedForTrash(
|
||||
trashEntries, modified: \.modified, title: \.title, name: { $0.id.rawValue })
|
||||
let verdict = dedupeIdentities(
|
||||
inBoardAt: boardRoot,
|
||||
lanes: orderedLanes,
|
||||
@@ -496,7 +506,7 @@ public enum BoardLoader: Sendable {
|
||||
private static func dedupeIdentities(
|
||||
inBoardAt root: URL,
|
||||
lanes: [WalkedLane],
|
||||
trash: [(id: ItemID, title: String?, order: Double)],
|
||||
trash: [(id: ItemID, title: String?, modified: Date?)],
|
||||
historyRanker: IdentityHistoryRanker?
|
||||
) -> IntegrityRules.DedupeVerdict {
|
||||
typealias Container = IntegrityRules.IdentityOccurrence.Container
|
||||
|
||||
Reference in New Issue
Block a user