Realign code with the 2026-07-31 rulings
The trash sorts by modified descending — the arrival rank mint retires (Ranks.isOrderedForTrash one comparator, loader + merged order agree; the legacy deleted: migration stamps modified from the tombstone timestamp where parseable; delete undo steps validate existence-only; agent guide v8). Trash selection goes kind-blind — ranges, marquee, Select All, and the successor walk sweep both kinds; the guard moves to the exits (mixed-payload drop refusal, copy/cut validation). The copy stamping preflight widens back to comment depth (load-scoped posture — the board always loads, the gesture refuses whole). Fixes a latent no-op: trashed-lane drag restore never fired (DragSession.beginLanes hard-coded the board container). 2403 tests in 413 suites green. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -638,27 +638,63 @@ struct CommentCopyTests {
|
||||
#expect(!fixture.exists("\(copiedCard)/comments/.trash"))
|
||||
}
|
||||
|
||||
@Test("A comment nobody can stamp never refuses the copy — it travels verbatim")
|
||||
func brokenCommentDoesNotRefuseACopy() throws {
|
||||
/// **The preflight reaches comment depth** (01-storage-format.md § Identity lifecycle and
|
||||
/// § Enhanced schema, ruled 2026-07-31 — reversing the 2026-07-30 carve-out this suite used to
|
||||
/// pin): "a comment whose frontmatter cannot take the stamp refuses the copy exactly like a card
|
||||
/// or lane — never-refuse … is a *load* posture, and a user-initiated copy is a transaction, not
|
||||
/// a load".
|
||||
///
|
||||
/// The board still loads with this comment on it — that is the other test in this file — and it
|
||||
/// is the gesture that refuses. Whole, and with nothing materialized: a partial copy is the one
|
||||
/// outcome the transaction rule exists to prevent.
|
||||
@Test("A comment nobody can stamp refuses the whole copy, and nothing is materialized")
|
||||
func brokenCommentRefusesACopy() throws {
|
||||
let fixture = try WriterFixture()
|
||||
defer { fixture.tearDown() }
|
||||
try fixture.item("", Item.board)
|
||||
try fixture.item(Ident.lane1, Item.rich(order: "1024", title: "Todo"))
|
||||
try fixture.item(Ident.lane2, Item.rich(order: "2048", title: "Doing"))
|
||||
try fixture.item("\(Ident.lane1)/\(Ident.card1)", Item.rich(order: "1024", title: "Fix login"))
|
||||
// Readable, uneditable — the shape that refuses a copy at card level.
|
||||
// Readable, uneditable — the shape that refuses a copy at card level, now at comment depth.
|
||||
try fixture.item("\(Ident.lane1)/\(Ident.card1)/comments/\(CommentIdent.one)", Item.uneditable)
|
||||
|
||||
let copy = try BoardWriter.copyItem(
|
||||
at: fixture.url("\(Ident.lane1)/\(Ident.card1)"),
|
||||
toParent: fixture.url(Ident.lane2),
|
||||
order: 1024,
|
||||
stamps: .fork
|
||||
)
|
||||
let copied = "\(Ident.lane2)/\(copy.rawValue)"
|
||||
let names = try postedNames(fixture, inCard: copied)
|
||||
#expect(names.count == 1, "the copy landed whole")
|
||||
#expect(try fixture.indexText("\(copied)/comments/\(names[0])") == Item.uneditable, "verbatim")
|
||||
var thrown: BoardWriteError?
|
||||
do {
|
||||
_ = try BoardWriter.copyItem(
|
||||
at: fixture.url("\(Ident.lane1)/\(Ident.card1)"),
|
||||
toParent: fixture.url(Ident.lane2),
|
||||
order: 1024,
|
||||
stamps: .fork
|
||||
)
|
||||
} catch {
|
||||
thrown = error
|
||||
}
|
||||
|
||||
let error = try #require(thrown)
|
||||
if case .uneditableFrontmatter = error.reason {} else {
|
||||
Issue.record("expected an uneditable-frontmatter refusal, got \(error.reason)")
|
||||
}
|
||||
#expect(error.path.hasSuffix("comments/\(CommentIdent.one)/index.md"),
|
||||
"the path points at the annotation, which is where the fix is")
|
||||
// Nothing landed, and the source is untouched.
|
||||
#expect(try fixture.entryNames(Ident.lane2).filter { IntegrityRules.isIdentityShaped($0) }.isEmpty)
|
||||
#expect(try fixture.indexText("\(Ident.lane1)/\(Ident.card1)/comments/\(CommentIdent.one)")
|
||||
== Item.uneditable)
|
||||
}
|
||||
|
||||
/// The other side of "load-scoped": the very same board **loads**, and its card window's thread
|
||||
/// read tolerates the annotation. Only the copy gesture refuses.
|
||||
@Test("The same broken comment never refuses the board")
|
||||
func brokenCommentNeverRefusesTheBoard() throws {
|
||||
let fixture = try WriterFixture()
|
||||
defer { fixture.tearDown() }
|
||||
try fixture.item("", Item.board)
|
||||
try fixture.item(Ident.lane1, Item.rich(order: "1024", title: "Todo"))
|
||||
try fixture.item("\(Ident.lane1)/\(Ident.card1)", Item.rich(order: "1024", title: "Fix login"))
|
||||
try fixture.item("\(Ident.lane1)/\(Ident.card1)/comments/\(CommentIdent.one)", Item.uneditable)
|
||||
|
||||
let model = try BoardLoader.load(boardRoot: fixture.root).model
|
||||
#expect(model.lanes.first?.cards.map(\.id.rawValue) == [Ident.card1])
|
||||
}
|
||||
|
||||
@Test("Template instantiation is born-today at comment depth too")
|
||||
@@ -739,7 +775,7 @@ struct CommentCopyTests {
|
||||
try fixture.item(Ident.lane1, Item.rich(order: "1024", title: "Todo"))
|
||||
let card = try threadedCard(fixture, in: Ident.lane1, card: Ident.card1)
|
||||
|
||||
try BoardWriter.deleteCardToTrash(at: fixture.url(card), inBoard: fixture.root, order: 1024)
|
||||
try BoardWriter.deleteCardToTrash(at: fixture.url(card), inBoard: fixture.root)
|
||||
|
||||
let trashedCard = ".trash/\(Ident.card1)"
|
||||
#expect(fixture.exists("\(trashedCard)/comments/\(CommentIdent.one)"))
|
||||
|
||||
Reference in New Issue
Block a user