Lanes delete into the trash — storage, loader, writer, and undo

Phase 1 of the lanes-in-trash card (2026-07-29 ruling, docs led the
code): lane delete is a move into .trash/ with the subtree intact,
arriving at top trash rank — no destructive delete remains outside
the trash.

TrashedLane opaque unit (id/schema/title/order/heldCards) beside
trash cards — deliberately not a Lane, so no card-shaped surface can
believe an empty subtree. Loader's trash walk trusts the kind VALUE
(lane → opaque unit w/ held-card count counted at the loader's own
unit; card → ordinary card; absent/unrecognized → UUID-children
shape, empty-kindless falls to card per 01's honest limit). Writer:
moveIntoTrash generalized with kind passed never derived (an empty
lane would re-derive as card), deleteLaneToTrash mints against the
whole-container rank ladder. Retired: migrateTombstonedLane (lane
deleted: now ignored — loads live, bytes inert, tolerate-tier
warning), removeLane, captureSubtree/recreateSubtree and the
subtree-snapshot machinery. Undo inverse = move back to captured
strip position, redo replays at captured trash rank. Purge walks
lane subtrees; TrashModel.Freight phrases confirms with lane freight
("…and its 5 cards"). ItemPath gains .trashLane; resolve interleaves
the trash by rank; SearchFilter matches lane rows by title only.
Trashed-lane card windows dismiss and pending cuts void via the
ordinary vanish rule — no new plumbing.

Phase 2 (rendering, selection grammar, drag, a11y, agent guide)
follows. Both schemes 1858 tests / 318 suites green.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-30 16:16:57 -04:00
parent 785ef5fe14
commit 8014bde7c6
21 changed files with 1357 additions and 910 deletions
+4 -4
View File
@@ -292,7 +292,7 @@ struct IntegrityDefectTests {
LooseCardFiles(laneID: lane, cardID: card, title: nil, fileNames: ["a.txt"])
).healClass == .looseCardFiles)
#expect(IntegrityRules.Defect.legacyTombstone(
LegacyTombstone(kind: .lane, laneID: lane, cardID: nil, title: nil)
LegacyTombstone(laneID: lane, cardID: card, title: nil)
).healClass == .legacyTombstone)
#expect(IntegrityRules.Defect.claimedNameSquatted(
ClaimedNameSquatter(name: ".trash", found: .file, expected: .directory)
@@ -318,10 +318,10 @@ struct IntegrityDefectTests {
@Test("Signatures identify the work, not its display")
func signaturesIdentifyTheWork() {
let one = IntegrityRules.Defect.legacyTombstone(
LegacyTombstone(kind: .card, laneID: lane, cardID: card, title: "Before")
LegacyTombstone(laneID: lane, cardID: card, title: "Before")
)
let two = IntegrityRules.Defect.legacyTombstone(
LegacyTombstone(kind: .card, laneID: lane, cardID: card, title: "After")
LegacyTombstone(laneID: lane, cardID: card, title: "After")
)
#expect(one.signatures == two.signatures)
}
@@ -334,7 +334,7 @@ struct IntegrityDefectTests {
LooseCardFiles(laneID: lane, cardID: card, title: nil, fileNames: ["x"])
)
let tombstone = IntegrityRules.Defect.legacyTombstone(
LegacyTombstone(kind: .card, laneID: lane, cardID: card, title: nil)
LegacyTombstone(laneID: lane, cardID: card, title: nil)
)
let squatter = IntegrityRules.Defect.claimedNameSquatted(
ClaimedNameSquatter(name: ".trash", found: .symlink, expected: .directory)