Comments, phase 1 — storage, writer primitives, and the undo inventory
The kind: comment field table lands in IntegrityRules (the per-kind hook's first exercise), CommentThread reads one card's thread window-scoped (the board walk stays O(cards)), and CommentWriter gains the five gestures: draft save, post (rename .draft to a fresh UUID, created/modified restamped in the bracket), edit, delete into comments/.trash/, and the purge with its crash-residue memo. Post and delete register move-based undo steps; draft saves, edits, and the purge deliberately register nothing (13's no-capture rule). Copy boundaries strip comments/.trash, carry .draft verbatim, and remint threads; comments graduates to a displacing claimed name, with .draft, .trash, and a comment's attachments claimed one level down. CommentPath classifies changed paths into the 06 verb family for later announcer/composer wiring. One stated narrowing pending a ruling (filed on the findings board): the copy transaction's refuse-whole preflight stays cards-and-lanes — an unstampable copied comment copies verbatim with a log line, because comment defects never refuse. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -349,15 +349,18 @@ struct CardClaimedNameTests {
|
||||
#expect(result.claimedNameSquatters.map(\.found) == [.symlink])
|
||||
}
|
||||
|
||||
/// **`comments` stays tolerated** — the timing principle, stated as the absence of a defect.
|
||||
@Test("A file on a card's comments is not displaced — the name is not load-bearing yet")
|
||||
func fileOnCommentsIsTolerated() throws {
|
||||
/// **`comments` graduated** (2026-07-30, with the comment storage) — the timing principle run
|
||||
/// forwards: the name became load-bearing, so its squatter joined the scheduled class exactly as
|
||||
/// 01 said it would ("a wrong-kind holder is a tolerated stray today and joins the scheduled class
|
||||
/// the day the name becomes load-bearing"). This test used to pin the absence of the defect.
|
||||
@Test("A file on a card's comments is displaced now that the thread consumes the name")
|
||||
func fileOnCommentsIsADefect() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
try fixture.file("\(Ident.lane1)/\(Ident.card1)/comments", Data("someday".utf8))
|
||||
|
||||
let result = try BoardLoader.load(boardRoot: fixture.root)
|
||||
#expect(result.claimedNameSquatters.isEmpty)
|
||||
#expect(result.claimedNameSquatters.map(\.name) == ["comments"])
|
||||
#expect(result.looseCardFiles.isEmpty, "a reserved name is not a loose file either")
|
||||
}
|
||||
|
||||
@@ -447,14 +450,14 @@ struct CardClaimedNameTests {
|
||||
#expect(try fixture.data("\(Ident.lane1)/\(Ident.card2)/attachments 2") == Data("two".utf8))
|
||||
}
|
||||
|
||||
/// The table is the only thing to edit when `comments` graduates — pinned so the split is a stated
|
||||
/// rule rather than an accident of the probe's implementation.
|
||||
@Test("The card-level table claims attachments and comments, and displaces only attachments")
|
||||
/// The table is the whole rule — pinned so the level-uniform claim is stated rather than being an
|
||||
/// accident of the probe's implementation. Both card-level names displace since `comments`
|
||||
/// graduated with the comment storage (2026-07-30).
|
||||
@Test("The card-level table claims attachments and comments, and both displace")
|
||||
func theTableStatesTheSplit() {
|
||||
let names = IntegrityRules.claimedCardChildNames
|
||||
#expect(names.map(\.name) == ["attachments", "comments"])
|
||||
#expect(names.allSatisfy { $0.expected == .directory })
|
||||
#expect(names.first { $0.name == "attachments" }?.displacesSquatters == true)
|
||||
#expect(names.first { $0.name == "comments" }?.displacesSquatters == false)
|
||||
#expect(names.allSatisfy { $0.displacesSquatters })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user