Materialize the trash — store, undo, and the container universe
Phase 2 swaps every consumer: Liveness and its ancestor walk are gone, replaced by ItemContainer — a UUID set plus the container side it lives on, presence the whole test, one selection boundary instead of the old liveness law. Deletion stages by place: board cards move to the trash at a store-minted head rank, trash-side delete is permanent behind its confirmation, Delete Immediately skips the trash from anywhere, lane delete captures the subtree and removes the folder. Restore has no method at all — moveCards resolves members in either container, so drag-out and cut-paste are the ordinary moves 13 calls them, registering ordinary Move steps. The delete inverse moves the card back to its captured lane and rank; redo replays the captured trash rank, a value the gesture actually wrote; lane undo recreates the subtree byte-faithfully in session. Purges register nothing — where 13's trash section contradicts its own Rules on that, Rules wins, filed for ruling. Staleness collapsed to present-or-absent: a container is a path, so a foreign restore fails the delete step's expectation structurally. Legacy tombstones migrate on the loose-file tail hook, cards oldest-first so minting above top reproduces the retired newest-first column, lanes returning live, one folded loss row naming both directions. Put Back, restoreByDrag, receiveRestoredCards, TrashEntry, and the kind machinery are deleted; the trash column renders the container correctly with its full face rework left to phase 3. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -54,9 +54,9 @@ private func makeBoard() throws -> WriterFixture {
|
||||
try fixture.item("\(Ident.lane1)/\(Ident.card1)", styled(order: "1024", title: "First", keys: ["background: fern", "iconColor: chalk"]))
|
||||
try fixture.item("\(Ident.lane1)/\(Ident.card2)", styled(order: "2048", title: "Second"))
|
||||
try fixture.item(Ident.lane2, styled(order: "2048", title: "Doing", keys: ["background: chalk", "icon: tray"]))
|
||||
try fixture.item("\(Ident.lane2)/\(Ident.card3)", styled(order: "1024", title: "Third", keys: ["deleted: 2026-03-03T09:00:00Z"]))
|
||||
try fixture.item("\(Ident.lane2)/\(Ident.card3)", styled(order: "1024", title: "Third"))
|
||||
try fixture.item(Ident.lane3, Item.uneditable)
|
||||
try fixture.item(Ident.lane4, styled(order: "4096", title: "Gone", keys: ["deleted: 2026-03-03T09:00:00Z"]))
|
||||
try fixture.item(Ident.lane4, styled(order: "4096", title: "Gone"))
|
||||
try fixture.item("\(Ident.lane4)/\(Ident.card4)", styled(order: "1024", title: "Hidden"))
|
||||
return fixture
|
||||
}
|
||||
@@ -256,26 +256,27 @@ struct StyleWriteTests {
|
||||
#expect(lane(lane1, in: model)?.background.isMissing == true)
|
||||
}
|
||||
|
||||
@Test("Vanished, tombstoned and hidden targets are skipped silently")
|
||||
@Test("Vanished and trashed targets are skipped silently")
|
||||
func skipsTargetsThatRenderNowhere() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
try fixture.move("\(Ident.lane2)/\(Ident.card3)", toTrash: Ident.card3)
|
||||
try FileManager.default.removeItem(at: fixture.url(Ident.lane4))
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let log = BracketLog()
|
||||
log.attach(to: store)
|
||||
let tombstoned = try fixture.indexData("\(Ident.lane2)/\(Ident.card3)")
|
||||
let hidden = try fixture.indexData("\(Ident.lane4)/\(Ident.card4)")
|
||||
let trashedBytes = try fixture.indexData(".trash/\(Ident.card3)")
|
||||
|
||||
// An id that names nothing, a tombstoned card, and a live card under a tombstoned lane —
|
||||
// "nothing is ever written into a vanished folder", ancestor walk included.
|
||||
// An id that names nothing, and a card that has been moved to the trash — "everything
|
||||
// edit-shaped is disabled on trash selections" (04 ▸ The trash), and "nothing is ever
|
||||
// written into a vanished folder".
|
||||
store.applyStyle(
|
||||
to: .items([ItemID(rawValue: Ident.indexless), card3, card4]),
|
||||
background: .set("obsidian")
|
||||
)
|
||||
|
||||
#expect(log.begins == 0)
|
||||
#expect(try fixture.indexData("\(Ident.lane2)/\(Ident.card3)") == tombstoned)
|
||||
#expect(try fixture.indexData("\(Ident.lane4)/\(Ident.card4)") == hidden)
|
||||
#expect(try fixture.indexData(".trash/\(Ident.card3)") == trashedBytes)
|
||||
#expect(store.banners.oneShots.isEmpty)
|
||||
}
|
||||
|
||||
@@ -314,13 +315,13 @@ struct StyleWriteTests {
|
||||
|
||||
// MARK: Subjects and levels
|
||||
|
||||
@Test("Subjects are the live targets in display order, with their current values")
|
||||
func subjectsAreLiveTargetsInDisplayOrder() throws {
|
||||
@Test("Subjects are the board's targets in display order, with their current values")
|
||||
func subjectsAreBoardTargetsInDisplayOrder() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
|
||||
let subjects = store.styleSubjects(of: .items([card2, card1, card3, lane1]))
|
||||
let subjects = store.styleSubjects(of: .items([card2, card1, lane1]))
|
||||
#expect(subjects.map(\.id) == [lane1, card1, card2], "lane first, then its cards top to bottom")
|
||||
#expect(subjects.map(\.background) == [.missing, .valid("fern"), .missing])
|
||||
#expect(subjects.last?.folder.lastPathComponent == Ident.card2)
|
||||
|
||||
Reference in New Issue
Block a user