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:
@@ -487,6 +487,91 @@ struct MoveUndoTests {
|
||||
#expect(try document(fixture, Ident.lane1).order.value == moved)
|
||||
}
|
||||
|
||||
/// **The inverses conform to the container-change predicate** (01-storage-format.md
|
||||
/// § Frontmatter ▸ `modified`'s scope, refined 2026-07-30) — the m8 conformance check, stated at
|
||||
/// the level the rule is about: an inverse is an ordinary app-mediated write, so it is subject to
|
||||
/// the *same* predicate as the gesture it inverts, not to a rule of its own.
|
||||
///
|
||||
/// Three claims in one round trip, because they are one claim: the undo of a within-lane reorder is
|
||||
/// itself a within-lane reorder and rewrites only `order`; the undo of a cross-lane move is itself a
|
||||
/// cross-lane move and stamps; and **no trash-specific branch exists in either direction** — the
|
||||
/// trash round trip stamps for the same reason the cross-lane one does.
|
||||
///
|
||||
/// It reads `modified-by` rather than `modified`, deliberately: `untouchedLines` filters the whole
|
||||
/// `modified*` family precisely because a content write is *expected* to move it, so the foreign
|
||||
/// stamp's survival is the assertion with a sharp edge — it survives an order-only rewrite and is
|
||||
/// cleared by a content one, and `Item.rich` plants one on every fixture card for exactly this.
|
||||
@Test("An inverse stamps only when it changes a container")
|
||||
func inversesFollowTheContainerPredicate() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let (store, history) = try makeStore(fixture)
|
||||
|
||||
// Within-lane, there and back: nothing on either leg is a content write.
|
||||
store.moveCards([card1], toLane: lane1, at: 2)
|
||||
#expect(try document(fixture, card1Path).rawValue(for: FrontmatterKeys.modifiedBy) == "claude")
|
||||
history.undo()
|
||||
#expect(
|
||||
try document(fixture, card1Path).rawValue(for: FrontmatterKeys.modifiedBy) == "claude",
|
||||
"undoing a reorder is a reorder — order-only, both ways"
|
||||
)
|
||||
|
||||
// Cross-lane, there and back: both legs change the container, so both stamp.
|
||||
store.moveCards([card2], toLane: lane2, at: 0)
|
||||
#expect(try document(fixture, "\(Ident.lane2)/\(Ident.card2)").rawValue(for: FrontmatterKeys.modifiedBy) == nil)
|
||||
// Re-planted by hand, standing in for an agent that stamped the card in its new lane — the
|
||||
// inverse has to clear it again, because moving back is itself a container change.
|
||||
try BoardWriter.updateIndex(
|
||||
inItemFolder: fixture.url("\(Ident.lane2)/\(Ident.card2)"), operation: .style(title: nil)
|
||||
) { $0.set(FrontmatterKeys.modifiedBy, to: .string("claude")) }
|
||||
history.undo()
|
||||
#expect(
|
||||
try document(fixture, card2Path).rawValue(for: FrontmatterKeys.modifiedBy) == nil,
|
||||
"undoing a cross-lane move is a cross-lane move — it stamps"
|
||||
)
|
||||
}
|
||||
|
||||
/// The lane half of the same claim: a lane's container is the board root and never changes, so a
|
||||
/// lane drag and its inverse are both order-only.
|
||||
@Test("A lane reorder and its inverse are both order-only")
|
||||
func laneInversesAreOrderOnly() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let (store, history) = try makeStore(fixture)
|
||||
|
||||
store.moveLane(lane1, toIndex: 1)
|
||||
#expect(try document(fixture, Ident.lane1).rawValue(for: FrontmatterKeys.modifiedBy) == "claude")
|
||||
history.undo()
|
||||
#expect(try document(fixture, Ident.lane1).rawValue(for: FrontmatterKeys.modifiedBy) == "claude")
|
||||
history.redo()
|
||||
#expect(try document(fixture, Ident.lane1).rawValue(for: FrontmatterKeys.modifiedBy) == "claude")
|
||||
}
|
||||
|
||||
/// The trash round trip, from the undo stack rather than the Writer: the delete stamps and its
|
||||
/// inverse — the move back out — stamps too. **Neither is a special case**; both are container
|
||||
/// changes, which is the whole of the refinement.
|
||||
@Test("A delete and its inverse both stamp, with no trash branch")
|
||||
func theTrashRoundTripStampsBothWays() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let (store, history) = try makeStore(fixture)
|
||||
|
||||
store.select([card1], in: .board)
|
||||
store.deleteSelection()
|
||||
#expect(fixture.exists(".trash/\(Ident.card1)"))
|
||||
#expect(try document(fixture, ".trash/\(Ident.card1)").rawValue(for: FrontmatterKeys.modifiedBy) == nil)
|
||||
|
||||
try BoardWriter.updateIndex(
|
||||
inItemFolder: fixture.url(".trash/\(Ident.card1)"), operation: .style(title: nil)
|
||||
) { $0.set(FrontmatterKeys.modifiedBy, to: .string("claude")) }
|
||||
history.undo()
|
||||
#expect(fixture.exists(card1Path))
|
||||
#expect(
|
||||
try document(fixture, card1Path).rawValue(for: FrontmatterKeys.modifiedBy) == nil,
|
||||
"restoring out of the trash is a container change and clears the stamp"
|
||||
)
|
||||
}
|
||||
|
||||
@Test("⌥⌘↓ undoes the whole permutation, siblings included")
|
||||
func sortRoundTrip() throws {
|
||||
let fixture = try makeBoard()
|
||||
@@ -735,7 +820,7 @@ struct NotUndoableTests {
|
||||
let armed = try #require(history.undoActionName)
|
||||
|
||||
store.select([trashed], in: .trash)
|
||||
store.deleteImmediately([trashed])
|
||||
store.deleteImmediately([trashed], in: .trash)
|
||||
|
||||
#expect(fixture.exists(trashedPath) == false)
|
||||
#expect(store.purgeIsUnrecoverable)
|
||||
|
||||
Reference in New Issue
Block a user