Realign code with the 2026-07-29 findings-resolution rulings
Nine rulings land as code. Reorders don't stamp — one container-change predicate (WriteOperation.rewritesOrderOnly): within-container reorders and the renumber rescale rewrite only order, while cross-lane, cross-board, and trash moves stamp modified and clear modified-by; no trash special case exists, and the m8 undo inverses conform through the same seam. Copies are transactions: the root-strict/nested-lenient split retires for a whole-subtree stampability preflight that refuses loudly naming the offender, and every item-level copy severs remote/remote-state at every level (whole-board forks carry them verbatim). Paste refuses, never degrades: the embedded-index.md materialization and its loss row retire; a missing staged snapshot produces nothing and posts an error-tone one-shot named from manifest metadata. Coerce-tier fallbacks log through the Defect stream with path context attached loader-side. Displacement is level-uniform: a file squatting attachments inside a card heals by the same rename ladder as board-root squatters; comments stays tolerated. Delete Immediately joins card and lane context menus as Delete's ⌥-alternate with its own VO custom action, routed through an explicit container so the menu target outranks standing selection. Agent guide v7 teaches the stamp discipline and the card-level attachments claim, and sheds two stale v6 lines (lanes trash now; kind is taught). Verified conformant, unchanged: edition-aware Undo/Redo disable, trash marquee full-height backdrop. Both schemes 1854 tests / 318 suites green; verify-editions 30/30. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -390,7 +390,7 @@ struct PurgeTests {
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
store.select([card1], in: .board)
|
||||
|
||||
store.deleteImmediately([card1])
|
||||
store.deleteImmediately([card1], in: .board)
|
||||
|
||||
#expect(!fixture.exists("\(Ident.lane1)/\(Ident.card1)"))
|
||||
#expect(!fixture.exists(".trash/\(Ident.card1)"), "03 ▸ Trash: ⌥⌘⌫ skips the trash from anywhere")
|
||||
@@ -404,7 +404,7 @@ struct PurgeTests {
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
store.select([trashed], in: .trash)
|
||||
|
||||
store.deleteImmediately([trashed])
|
||||
store.deleteImmediately([trashed], in: .trash)
|
||||
|
||||
#expect(!fixture.exists(".trash/\(Ident.indexless)"))
|
||||
#expect(fixture.exists(".trash/\(More.newer)"), "and only what it named")
|
||||
@@ -417,7 +417,7 @@ struct PurgeTests {
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
store.select([lane3], in: .board)
|
||||
|
||||
store.deleteImmediately([lane3])
|
||||
store.deleteImmediately([lane3], in: .board)
|
||||
|
||||
#expect(fixture.exists(Ident.lane3))
|
||||
}
|
||||
@@ -473,7 +473,7 @@ struct PurgeTests {
|
||||
|
||||
store.select([trashed], in: .trash)
|
||||
store.deleteTrashCards([trashed])
|
||||
store.deleteImmediately([newer])
|
||||
store.deleteImmediately([newer], in: .trash)
|
||||
store.emptyTrash()
|
||||
|
||||
// 13-native-undo.md ▸ Rules: "Permanently delete (Delete Immediately, Empty Trash) …
|
||||
@@ -745,7 +745,7 @@ struct TrashConfirmationsTests {
|
||||
|
||||
let pending = try #require(confirmations.pending)
|
||||
#expect(pending.prompt.title == "Permanently delete \u{201C}Trashed\u{201D}?")
|
||||
#expect(pending.action == .purge([trashed]))
|
||||
#expect(pending.action == .purge([trashed], .trash))
|
||||
// Nothing has happened yet — the alert is what stands between the keystroke and the loss.
|
||||
#expect(fixture.exists(".trash/\(Ident.indexless)"))
|
||||
|
||||
@@ -756,6 +756,72 @@ struct TrashConfirmationsTests {
|
||||
confirmations.confirm(in: store)
|
||||
}
|
||||
|
||||
/// **The card and lane context menus' ⌥-alternate** — Delete Immediately, routed through
|
||||
/// `requestBoardPurge` rather than through `requestPurge` (11-command-nexus.md ▸ Context menus'
|
||||
/// Card and Lane rows: "Delete — with Delete Immediately as its ⌥-alternate").
|
||||
/// `purgeConfirmsThenActs`'s twin for the board side: same alert, same rule, a board card as the
|
||||
/// target instead of a trash one.
|
||||
@Test("The board-side ⌥-alternate raises the same alert, and purges the board card on confirm")
|
||||
func boardPurgeConfirmsThenActs() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let confirmations = TrashConfirmations()
|
||||
|
||||
confirmations.requestBoardPurge(of: [card1], in: store)
|
||||
|
||||
let pending = try #require(confirmations.pending)
|
||||
#expect(pending.prompt.title == "Permanently delete \u{201C}First\u{201D}?")
|
||||
#expect(pending.action == .purge([card1], .board))
|
||||
// Nothing has happened yet — same alert, same rule.
|
||||
#expect(fixture.exists("\(Ident.lane1)/\(Ident.card1)"))
|
||||
|
||||
confirmations.confirm(in: store)
|
||||
#expect(!fixture.exists("\(Ident.lane1)/\(Ident.card1)"))
|
||||
#expect(!fixture.exists(".trash/\(Ident.card1)"), "skips the trash — purged, not moved")
|
||||
#expect(confirmations.pending == nil)
|
||||
}
|
||||
|
||||
/// A context menu names its target by where it was invoked, so a card row's Delete Immediately
|
||||
/// must purge the *clicked* card even while a different card is selected — `TrashMenuValidation
|
||||
/// Tests.contextMenuDeleteIgnoresTheSelection`'s claim, mirrored onto the board side.
|
||||
@Test("The board-side ⌥-alternate acts on its own target, not the standing selection")
|
||||
func boardPurgeIgnoresTheSelection() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let confirmations = TrashConfirmations()
|
||||
// A right-click on `card1` without first selecting it must still purge `card1`, never the
|
||||
// card the standing selection happens to hold (`CardFaceView.targetIDs`'s targeting rule).
|
||||
store.select([card2], in: .board)
|
||||
|
||||
confirmations.requestBoardPurge(of: [card1], in: store)
|
||||
let pending = try #require(confirmations.pending)
|
||||
#expect(pending.action == .purge([card1], .board))
|
||||
|
||||
confirmations.confirm(in: store)
|
||||
#expect(!fixture.exists("\(Ident.lane1)/\(Ident.card1)"), "the clicked card is gone")
|
||||
#expect(fixture.exists("\(Ident.lane1)/\(Ident.card2)"), "the selected card was never the subject")
|
||||
}
|
||||
|
||||
/// `TrashModel.canDeleteImmediately` is cards only (`TrashValidationTests
|
||||
/// .canDeleteImmediatelyIsCardsOnly`: "a lane's delete is physical already … there is nothing for
|
||||
/// 'skip the trash' to mean on one"), and the lane row's alternate inherits that unchanged: it is
|
||||
/// wired per 11-command-nexus.md's Lane row, but presently inert on a lane-only target — the same
|
||||
/// posture File ▸ Delete Immediately already takes on a lane-only selection.
|
||||
@Test("A lane-only target raises no prompt — the alternate is still cards only")
|
||||
func boardPurgeIsStillCardsOnlyForALaneTarget() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let confirmations = TrashConfirmations()
|
||||
|
||||
confirmations.requestBoardPurge(of: [lane1], in: store)
|
||||
|
||||
#expect(confirmations.pending == nil)
|
||||
#expect(fixture.exists(Ident.lane1))
|
||||
}
|
||||
|
||||
/// 03-board-ui.md § Trash: "on a trash card, Delete (⌫/⌘⌫) is permanent … Both confirm exactly
|
||||
/// where the loss is real."
|
||||
@Test("The trash's own Delete confirms; the board's goes straight through")
|
||||
|
||||
Reference in New Issue
Block a user