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:
@@ -106,8 +106,23 @@ enum BoardTreeCopy {
|
||||
throw .failed(url: source, error: error)
|
||||
}
|
||||
|
||||
// The one exclusion this walk makes without being asked (01-storage-format.md § Enhanced
|
||||
// schema: `comments/.trash/` is "**stripped at every copy boundary** (clipboard staging,
|
||||
// Duplicate, Save as Template) — a copy must not carry ghosts no window session will ever
|
||||
// purge"). Unconditional because all three of this walk's callers *are* copy boundaries, and
|
||||
// an opt-in flag would be three call sites agreeing to one rule instead of one rule.
|
||||
//
|
||||
// It is the one depth-sensitive exclusion, hence the parent check rather than a name in
|
||||
// `excluded`: `.trash` at a board root is the board's own and carries or not per each flow's
|
||||
// stated exclusions, while `.trash` inside a `comments/` is undo's backing store and never
|
||||
// travels.
|
||||
let isCommentThread = source.lastPathComponent.lowercased() == IntegrityRules.commentsFolderName
|
||||
|
||||
for entry in entries.sorted(by: { $0.lastPathComponent < $1.lastPathComponent }) {
|
||||
guard !excluded.contains(entry.lastPathComponent.lowercased()) else { continue }
|
||||
guard !isCommentThread
|
||||
|| entry.lastPathComponent.lowercased() != IntegrityRules.commentTrashFolderName
|
||||
else { continue }
|
||||
|
||||
// Between items, never mid-item: this is the whole of "checks cancellation between
|
||||
// items", and the reason the copy is a walk at all.
|
||||
|
||||
@@ -477,6 +477,13 @@ public final class ClipboardStore {
|
||||
/// makes the next paste **refuse whole**, naming it (`perform`'s preflight), rather than
|
||||
/// materializing it hollow from the manifest's embedded `index.md`. Failing to stage is therefore
|
||||
/// as loud as it should be, one gesture later.
|
||||
/// **Staging is a copy boundary**, so `comments/.trash/` does not survive it (01-storage-format.md
|
||||
/// § Enhanced schema: "stripped at every copy boundary (clipboard staging, Duplicate, Save as
|
||||
/// Template)"). Stripped from the *snapshot* rather than skipped during it, because the snapshot is
|
||||
/// one monolithic `copyItem` — and stripped through the Writer's own call so the rule is one
|
||||
/// function (`BoardWriter.stripCommentTrash`), not a second reading of it here. Best-effort like
|
||||
/// the copy above it: a snapshot that could not be tidied is still a snapshot, and the paste that
|
||||
/// materializes from it strips again at its own boundary.
|
||||
private func stage(_ jobs: [StagingJob], into stagingDir: URL) {
|
||||
enqueue { [jobs, stagingDir] in
|
||||
guard (try? FileManager.default.createDirectory(
|
||||
@@ -485,6 +492,7 @@ public final class ClipboardStore {
|
||||
)) != nil else { return }
|
||||
for job in jobs {
|
||||
try? FileManager.default.copyItem(at: job.source, to: job.destination)
|
||||
try? BoardWriter.stripCommentTrash(under: job.destination, operation: .copy(title: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user