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:
@@ -179,6 +179,29 @@ struct WriteFidelityMinimalTouchTests {
|
||||
}
|
||||
#expect(BoardLoader.isUUIDShaped(createdCard))
|
||||
|
||||
// A **lane** delete is the same move, one level up (03 § Trash, re-ruled 2026-07-29): the
|
||||
// lane's folder leaves the visible tree carrying its whole subtree, and the cards inside it
|
||||
// are neither read nor rewritten — their mtimes prove it, since they are still tracked here
|
||||
// under their new path only after the restore puts the lane back.
|
||||
try step(
|
||||
"delete lane",
|
||||
targeting: [],
|
||||
departed: [Ident.lane2, "\(Ident.lane2)/\(Ident.card3)", "\(Ident.lane2)/\(Ident.card4)"]
|
||||
) {
|
||||
try BoardWriter.deleteLaneToTrash(
|
||||
at: fixture.url(Ident.lane2), inBoard: fixture.root, order: 512
|
||||
)
|
||||
}
|
||||
try step("restore lane", targeting: []) {
|
||||
_ = try BoardWriter.moveItem(
|
||||
at: BoardWriter.trashFolder(inBoard: fixture.root).appendingPathComponent(Ident.lane2),
|
||||
toParent: fixture.root,
|
||||
sourceBoardRoot: fixture.root,
|
||||
destinationBoardRoot: fixture.root,
|
||||
order: 2048
|
||||
)
|
||||
}
|
||||
|
||||
try step("import attachment", targeting: []) {
|
||||
// Dot-prefixed so it never becomes a board-root stray the loader has to warn about
|
||||
// — the source lives outside the board tree in spirit, just not in path.
|
||||
@@ -547,6 +570,26 @@ struct WriteFidelityStampingTests {
|
||||
#expect(restored.modifiedBy == nil, "out of the trash is a container change too")
|
||||
}
|
||||
|
||||
/// A lane's trash move is the same container change, and its **subtree is not** — the nested
|
||||
/// card is neither read nor rewritten, so its stamps are exactly what they were.
|
||||
@Test("A lane's trash move stamps the lane and nothing beneath it")
|
||||
func aLaneTrashMoveStampsOnlyTheLane() throws {
|
||||
let fixture = try twoLaneBoard()
|
||||
defer { fixture.tearDown() }
|
||||
|
||||
try BoardWriter.deleteLaneToTrash(
|
||||
at: fixture.url(Ident.lane1), inBoard: fixture.root, order: 1024
|
||||
)
|
||||
|
||||
let lane = try stamps(fixture, ".trash/\(Ident.lane1)")
|
||||
#expect(lane.modified != Self.priorModified, "into the trash is a container change")
|
||||
#expect(lane.modifiedBy == nil)
|
||||
|
||||
let card = try stamps(fixture, ".trash/\(Ident.lane1)/\(Ident.card1)")
|
||||
#expect(card.modified == Self.priorModified, "the freight's own container did not change")
|
||||
#expect(card.modifiedBy == "claude", "so even a foreign stamp survives, untouched")
|
||||
}
|
||||
|
||||
/// A cross-board arrival changes the container as surely as a cross-lane move does, and the
|
||||
/// import boundary's remint does not change that: the arrived file is stamped either way.
|
||||
@Test("A cross-board arrival stamps")
|
||||
|
||||
Reference in New Issue
Block a user