Selection liveness is effective — ancestor-walked (ratified)

The parallel design session settled the tombstoned-ancestor question:
a card counts as trashed if its own flag or its lane's says so, so
tombstoning a lane ejects its cards from a live selection — they render
nowhere once the lane collapses to a single trash entry, and nothing
invisible may stay selected, drag-included, or pending-cut. One-line
predicate change in Selection.resolved(against:), plus the test.

Full BoardStore suite green (15 tests).

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
This commit is contained in:
2026-07-26 19:48:12 -04:00
parent f0e1738964
commit cb86316506
2 changed files with 25 additions and 4 deletions
+19
View File
@@ -393,6 +393,25 @@ struct BoardStoreTests {
#expect(store.selection.ids == [ItemID(rawValue: Ident.card2)])
}
@Test("Tombstoning a lane ejects its cards from a live selection — liveness is effective")
func selectionEjectsCardsUnderATombstonedLane() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let store = try BoardStore(rootURL: fixture.root)
store.select([ItemID(rawValue: Ident.card1), ItemID(rawValue: Ident.card2)], liveness: .live)
try fixture.item(Ident.lane1, tombstoned(order: "1024", title: "Lane one"))
store.handleWatcherEvent(.treeChanged(.foreign))
await store.awaitQuiescence()
// The cards' own flags never changed, but their lane's did and liveness is
// ancestor-walked (02, settled): the cards render nowhere once 03 collapses the lane to
// a single trash entry, and nothing invisible may stay selected.
let survivor = lane(Ident.lane1, in: store.snapshot)?.cards.first { $0.id.rawValue == Ident.card1 }
#expect(survivor?.isDeleted == false, "the card's own flag is untouched")
#expect(store.selection.ids.isEmpty)
}
@Test("A selection can resolve to nothing, and nothing is invented to replace it")
func selectionCanResolveToNothing() async throws {
let fixture = try makeBoard()