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:
2026-07-31 18:35:07 -04:00
parent 542ab169a3
commit bec75e4282
37 changed files with 1200 additions and 551 deletions
+22 -15
View File
@@ -28,8 +28,11 @@ private enum More {
static let cardF = "ffffffff-ffff-4fff-8fff-ffffffffffff"
}
private func card(order: String, title: String) -> String {
"---\nschema: 1\ntitle: \(title)\norder: \(order)\n---\n\(title) body.\n"
private func card(order: String, title: String, modified: String? = nil) -> String {
// The trash sorts by `modified` descending (01-storage-format.md § Deletion, re-ruled
// 2026-07-31), so a trash fixture states its column position here rather than in `order`.
let stamp = modified.map { "modified: \($0)\n" } ?? ""
return "---\nschema: 1\ntitle: \(title)\norder: \(order)\n\(stamp)---\n\(title) body.\n"
}
private func untitled(order: String) -> String {
@@ -48,10 +51,11 @@ private func makeBoard() throws -> WriterFixture {
try fixture.item(More.laneA, card(order: "1024", title: "Todo"))
try fixture.item("\(More.laneA)/\(Ident.card1)", card(order: "1024", title: "First"))
try fixture.item("\(More.laneA)/\(Ident.card2)", card(order: "2048", title: "Second"))
// Newest-first by ordinary ranks: every arrival mints above the current top.
try fixture.item(".trash/\(More.cardD)", card(order: "1024", title: "Oldest"))
try fixture.item(".trash/\(More.cardE)", card(order: "512", title: "Middle"))
try fixture.item(".trash/\(More.cardF)", card(order: "256", title: "Newest"))
// Newest-first by `modified`; the ranks disagree on purpose, so nothing here can pass by
// accident of the retired arrival-rank rule.
try fixture.item(".trash/\(More.cardD)", card(order: "256", title: "Oldest", modified: "2026-05-01T09:00:00Z"))
try fixture.item(".trash/\(More.cardE)", card(order: "512", title: "Middle", modified: "2026-05-03T09:00:00Z"))
try fixture.item(".trash/\(More.cardF)", card(order: "1024", title: "Newest", modified: "2026-05-05T09:00:00Z"))
return fixture
}
@@ -70,16 +74,16 @@ private let cardF = ItemID(rawValue: More.cardF)
@Suite("The trash's contents are the container")
struct TrashContentsTests {
@Test("The trash is `snapshot.trash`, newest first by ordinary ranks")
@Test("The trash is `snapshot.trash`, newest first by `modified`")
func theContainerIsTheList() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let snapshot = try load(fixture)
#expect(snapshot.trash.compactMap(\.title.value) == ["Newest", "Middle", "Oldest"],
"03 ▸ Trash: the trash sorts by `order` like any lane, and entry is at the top")
"03 ▸ Trash: the trash sorts by `modified` descending, and entry is at the top")
#expect(snapshot.trash.allSatisfy { $0.deleted.isMissing },
"there is no `deleted:` key and no timestamp sort")
"there is no `deleted:` key — the stamp is the position")
}
@Test("Lanes are never in it, whatever a hand-editor nests in there")
@@ -306,8 +310,11 @@ struct TrashFreightTests {
let fixture = try WriterFixture()
try fixture.item("", Item.board)
try fixture.item(More.laneA, card(order: "1024", title: "Todo"))
try fixture.item(".trash/\(More.cardD)", card(order: "1024", title: "A card"))
try fixture.item(".trash/\(Ident.lane2)", "---\nschema: 1\ntitle: Doing\norder: 512\nkind: lane\n---\n")
try fixture.item(
".trash/\(More.cardD)", card(order: "1024", title: "A card", modified: "2026-05-01T09:00:00Z"))
try fixture.item(
".trash/\(Ident.lane2)",
"---\nschema: 1\ntitle: Doing\norder: 512\nmodified: 2026-05-03T09:00:00Z\nkind: lane\n---\n")
for index in 0 ..< held {
try fixture.item(
".trash/\(Ident.lane2)/\(UUID().uuidString.lowercased())",
@@ -396,15 +403,15 @@ struct TrashFreightTests {
== [.trashLane(ItemID(rawValue: Ident.lane2))])
}
/// The column is one list: `resolve` hands back the trash's paths interleaved by rank, because
/// a batch's order is the column's order (03 § Trash).
@Test("Resolution interleaves the container's two kinds by rank")
/// The column is one list: `resolve` hands back the trash's paths interleaved by `modified`,
/// because a batch's order is the column's order (03 § Trash).
@Test("Resolution interleaves the container's two kinds by the column's own order")
func resolutionInterleaves() throws {
let fixture = try makeFreightBoard(held: 0)
defer { fixture.tearDown() }
let snapshot = try load(fixture)
// The lane sits at 512 and the card at 1024, so the lane row comes first.
// The lane's stamp is the newer of the two, so the lane row comes first.
#expect(ItemPath.resolve(
[ItemID(rawValue: Ident.lane2), cardD], in: .trash, snapshot: snapshot
) == [.trashLane(ItemID(rawValue: Ident.lane2)), .trashCard(cardD)])