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:
@@ -93,6 +93,29 @@ struct WriterFixture {
|
||||
func entryNames(_ relativePath: String) throws -> [String] {
|
||||
try FileManager.default.contentsOfDirectory(atPath: url(relativePath).path).sorted()
|
||||
}
|
||||
|
||||
/// Moves a folder from one place in the board to another — a **foreign** move, the way an agent
|
||||
/// or a hand-editor makes one: `FileManager` and nothing else, no `index.md` rewritten, no rank
|
||||
/// touched. What the container-crossing rules are stated in terms of (02-architecture.md §
|
||||
/// Live-reload resilience, resettled 2026-07-28).
|
||||
func moveFolder(_ relativePath: String, to destinationPath: String) throws {
|
||||
let destination = url(destinationPath)
|
||||
try FileManager.default.createDirectory(
|
||||
at: destination.deletingLastPathComponent(),
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
try FileManager.default.moveItem(at: url(relativePath), to: destination)
|
||||
}
|
||||
|
||||
/// A card folder moved into `<root>/.trash/` — the shape of a delete on disk, made foreignly.
|
||||
func move(_ relativePath: String, toTrash cardName: String) throws {
|
||||
try moveFolder(relativePath, to: ".trash/\(cardName)")
|
||||
}
|
||||
|
||||
/// A card folder moved out of the trash into a lane — the shape of a restore, made foreignly.
|
||||
func move(_ relativePath: String, toLane laneName: String, card cardName: String) throws {
|
||||
try moveFolder(relativePath, to: "\(laneName)/\(cardName)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Move/copy identities
|
||||
|
||||
Reference in New Issue
Block a user