Every comment-trash purge kneels to the ownership gate — the container-whole retirement retires

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-06 17:10:09 -04:00
parent a60d97689e
commit ea15d1ac74
7 changed files with 449 additions and 55 deletions
+39
View File
@@ -105,6 +105,21 @@ final class CardWindowSession: CardSessionFlushing {
/// object's to run, which is also true.
var registerSessionStep: (@MainActor (CardWindowUndo, @escaping @MainActor () -> Void) -> Bool)?
/// **Where the close gives up this window's ownership of its card's comment trash**
/// `BoardStore.cardWindowDidClose(inCard:)`, wired by the host beside `registerSessionStep`
/// (13-native-undo.md Interaction with the trash, ruled 2026-08-06):
///
/// > "an open card window is itself an owner of its card's comment trash a retirement firing
/// > while the card's window is open defers its purge to that window's close ... the close then
/// > settles by the same gate."
///
/// A seam rather than a store reference, `registerSessionStep`'s reason: this object is a
/// lifecycle and stays testable by having no idea what a board is. `nil` a window that never
/// joined its board owns nothing on any store, which is also true.
///
/// Its **order** is the load-bearing part, and `endSession()` is where it is spelled.
var endCardWindowOwnership: (@MainActor () -> Void)?
/// The Edit buffer's dirty text, a typed-in raw-source outlet, or an inline comment edit session
/// holding keystrokes its file has not got see `CardSessionFlushing`.
///
@@ -199,6 +214,15 @@ final class CardWindowSession: CardSessionFlushing {
// still restores comments out of it. Registering answers whether the step took the purge on
// and a board whose substrate keeps no steps has already run it by the time that answer comes
// back, which is how Pro keeps purging at the close flush without a word about tiers here.
//
// **This window stops owning its card's comment trash first** (ruled 2026-08-06 the
// open-window carve-out). While the window is open the store defers every purge of this
// card's trash, because the entries this session deleted are backed by *this* stack's fine
// steps and the board-stack inventory cannot see them. The close is where that stops being
// true, and it has to stop being true **before** either branch below runs: a no-step close
// that unmarked itself afterwards would defer its own purge into a no-op, and a coarse step
// registered here can retire in the same breath (its registration clears the redo stack).
endCardWindowOwnership?()
let purge: @MainActor () -> Void = { [comments] in comments.purgeTrashNow() }
if registerSessionStep?(undo, purge) != true {
purge()
@@ -609,7 +633,17 @@ struct CardWindowHost: View {
/// The pane's two window-scoped facts are set *before* the read, because both of them are things
/// the read's results are resolved against: the folder every comment's attachments hang off, and
/// whether the lock is on.
///
/// **And the window takes ownership of its card's comment trash here** (13-native-undo.md
/// Interaction with the trash, ruled 2026-08-06): "an open card window is itself an owner of its
/// card's comment trash". From this line until `CardWindowSession.endSession` gives it back, a
/// purge of this card's trash a foreign step's retirement, another window's close defers
/// entirely, because the fine steps backing this session's deletes live on a stack the board's
/// inventory cannot see. The sweep below is unaffected and runs as it always has: it is gated on
/// `backedContent`, and a window that has not made a gesture yet holds nothing that gate is
/// missing.
private func openCommentThread(store: BoardStore) {
store.cardWindowDidOpen(inCard: ref.cardIdentity)
if case let .shows(placement) = Self.cardWindowFate(cardID: ref.cardID, in: store.snapshot) {
session.comments.cardFolder = Self.cardFolder(root: store.rootURL, placement: placement)
}
@@ -689,6 +723,11 @@ struct CardWindowHost: View {
session.registerSessionStep = { [weak store] undo, purge in
store?.registerCardSession(undo, inCard: cardID, retiring: purge) ?? false
}
// The close half of the open-window carve-out (13 Interaction with the trash, ruled
// 2026-08-06). Its open half is `CardWindowHost.openCommentThread`, beside the residue sweep;
// the pair has to be wired from the two places the window already calls into the store, and
// these are they. A released store owns nothing to give up, which is what `weak` says.
session.endCardWindowOwnership = { [weak store] in store?.cardWindowDidClose(inCard: cardID) }
}
/// Points the comments pane at its card **the one place every comment gesture learns which card