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:
+113
-125
@@ -528,14 +528,34 @@ struct PasteCutTests {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - The staging-less fallback
|
||||
// MARK: - Refuse, never degrade
|
||||
|
||||
/// **04-interactions.md ▸ Clipboard, re-ruled 2026-07-29** — Finder's invariant adopted:
|
||||
///
|
||||
/// > A paste whose staged snapshot is missing or unreadable refuses loudly — never degrades …
|
||||
/// > the paste produces **nothing**, and a one-shot failure banner names it from the manifest's
|
||||
/// > metadata. An item arrives **whole — index, attachments, loose files, and comments when they ship
|
||||
/// > — or not at all** … The refusal is transactional — all-or-nothing for the whole paste.
|
||||
///
|
||||
/// These are the former `PasteFallbackTests`, turned around: every case that used to assert an item
|
||||
/// materialized from the manifest's embedded `index.md` now asserts that **nothing** was written and a
|
||||
/// failure banner names the entry. The manifest still embeds the text — it is what names the entry in
|
||||
/// the sentence below — it is simply never a materialization source.
|
||||
@MainActor
|
||||
@Suite("Paste ▸ the staging-less fallback")
|
||||
struct PasteFallbackTests {
|
||||
@Suite("Paste ▸ refuse, never degrade")
|
||||
struct PasteRefusalTests {
|
||||
|
||||
@Test("A missing snapshot falls back to the embedded index.md, byte-faithfully")
|
||||
func fallbackWritesTheSourceBytes() async throws {
|
||||
/// Drops the staged tree the way the world does: a sweep that ran early, an unreadable container,
|
||||
/// a full disk mid-copy.
|
||||
private func loseTheSnapshot(_ harness: ClipboardHarness) throws {
|
||||
let copyID = try #require(harness.clipboard.payload?.copyID)
|
||||
try FileManager.default.removeItem(
|
||||
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
|
||||
)
|
||||
}
|
||||
|
||||
@Test("A missing snapshot writes nothing at all")
|
||||
func aMissingSnapshotWritesNothing() async throws {
|
||||
let harness = try makeClipboardHarness()
|
||||
defer { harness.tearDown() }
|
||||
let destination = try makeDestination()
|
||||
@@ -545,31 +565,21 @@ struct PasteFallbackTests {
|
||||
harness.store.select([clipboardCard1], in: .board)
|
||||
harness.clipboard.copy(from: harness.store)
|
||||
await harness.clipboard.stagingSettled()
|
||||
// The snapshot goes — a swept tree, a full disk, an unreadable container.
|
||||
let copyID = try #require(harness.clipboard.payload?.copyID)
|
||||
try FileManager.default.removeItem(
|
||||
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
|
||||
)
|
||||
try loseTheSnapshot(harness)
|
||||
|
||||
target.select([destinationLane], in: .board)
|
||||
await harness.clipboard.paste(into: target)?.value
|
||||
|
||||
let arrived = try #require(try pastedIDs(destinationLane, in: destination).last)
|
||||
let document = try FrontmatterDocument.parse(destination.indexText("\(Ident.lane4)/\(arrived)"))
|
||||
#expect(document.title.value == "First")
|
||||
// Content intact: unknown keys, the comment's key, and the body all survived.
|
||||
#expect(document.value(for: "project") != nil)
|
||||
#expect(document.value(for: "labels") != nil)
|
||||
#expect(document.body.contains("First body — with *markdown*"))
|
||||
// `created` kept, fresh `order`.
|
||||
#expect(document.created.value == ISO8601DateFormatter().date(from: "2026-01-01T09:00:00Z"))
|
||||
#expect(document.order.value != 1024)
|
||||
// Attachments absent — which is exactly what the banner is about to say.
|
||||
#expect(!destination.exists("\(Ident.lane4)/\(arrived)/attachments"))
|
||||
#expect(
|
||||
try pastedTitles(destinationLane, in: destination) == ["Resident"],
|
||||
"the destination holds exactly what it held before"
|
||||
)
|
||||
}
|
||||
|
||||
@Test("A degraded paste banners, naming exactly what was lost")
|
||||
func fallbackBanners() async throws {
|
||||
/// 04's own example sentence, end to end: the entry is named from the manifest's metadata, which is
|
||||
/// the whole reason the embedded `index.md` is still carried.
|
||||
@Test("The refusal banners as a failure, naming the entry from the manifest")
|
||||
func theRefusalBanners() async throws {
|
||||
let harness = try makeClipboardHarness()
|
||||
defer { harness.tearDown() }
|
||||
let destination = try makeDestination()
|
||||
@@ -579,43 +589,43 @@ struct PasteFallbackTests {
|
||||
harness.store.select([clipboardCard1], in: .board)
|
||||
harness.clipboard.copy(from: harness.store)
|
||||
await harness.clipboard.stagingSettled()
|
||||
let copyID = try #require(harness.clipboard.payload?.copyID)
|
||||
try FileManager.default.removeItem(
|
||||
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
|
||||
)
|
||||
try loseTheSnapshot(harness)
|
||||
|
||||
target.select([destinationLane], in: .board)
|
||||
await harness.clipboard.paste(into: target)?.value
|
||||
|
||||
#expect(target.banners.losses.map(\.message) == ["Pasted 'First' without its 2 attachments"])
|
||||
#expect(target.banners.losses.isEmpty, "the degraded paste's loss row is retired")
|
||||
#expect(target.banners.oneShots.count == 1)
|
||||
let error = try #require(target.banners.oneShots.first).error
|
||||
#expect(BannerCenter.headline(for: error) == "Couldn't paste 'First' — the copied content is gone")
|
||||
}
|
||||
|
||||
@Test("A fallback that lost nothing says nothing")
|
||||
func fallbackWithoutAttachmentsIsSilent() async throws {
|
||||
/// **The attachment-less case refuses too**, which is the pivot at its sharpest: under the degraded
|
||||
/// rule this entry pasted *silently* — its content was intact and it had no attachments to lose, so
|
||||
/// nothing was reported. Refuse-don't-degrade does not ask what would have been lost; the bytes the
|
||||
/// paste was to reproduce are gone, so there is nothing honest to write.
|
||||
@Test("An entry with no attachments refuses just the same")
|
||||
func anAttachmentLessEntryRefusesToo() async throws {
|
||||
let harness = try makeClipboardHarness()
|
||||
defer { harness.tearDown() }
|
||||
let destination = try makeDestination()
|
||||
defer { destination.tearDown() }
|
||||
let target = try BoardStore(rootURL: destination.root)
|
||||
|
||||
// `card2` has no attachments, so a fallback loses nothing at all.
|
||||
harness.store.select([clipboardCard2], in: .board)
|
||||
harness.clipboard.copy(from: harness.store)
|
||||
await harness.clipboard.stagingSettled()
|
||||
let copyID = try #require(harness.clipboard.payload?.copyID)
|
||||
try FileManager.default.removeItem(
|
||||
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
|
||||
)
|
||||
try loseTheSnapshot(harness)
|
||||
|
||||
target.select([destinationLane], in: .board)
|
||||
await harness.clipboard.paste(into: target)?.value
|
||||
|
||||
#expect(try pastedTitles(destinationLane, in: destination) == ["Resident", "Second"])
|
||||
#expect(target.banners.losses.isEmpty)
|
||||
#expect(try pastedTitles(destinationLane, in: destination) == ["Resident"])
|
||||
#expect(target.banners.oneShots.count == 1)
|
||||
}
|
||||
|
||||
@Test("A lane's fallback materializes its embedded cards")
|
||||
func laneFallbackCarriesItsCards() async throws {
|
||||
@Test("A lane payload refuses whole — no lane, no cards")
|
||||
func aLanePayloadRefusesWhole() async throws {
|
||||
let harness = try makeClipboardHarness()
|
||||
defer { harness.tearDown() }
|
||||
let destination = try makeDestination()
|
||||
@@ -625,115 +635,93 @@ struct PasteFallbackTests {
|
||||
harness.store.select([clipboardLane1], in: .board)
|
||||
harness.clipboard.copy(from: harness.store)
|
||||
await harness.clipboard.stagingSettled()
|
||||
let copyID = try #require(harness.clipboard.payload?.copyID)
|
||||
try FileManager.default.removeItem(
|
||||
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
|
||||
)
|
||||
let before = try pasted(destination).lanes.map(\.id)
|
||||
try loseTheSnapshot(harness)
|
||||
|
||||
await harness.clipboard.paste(into: target)?.value
|
||||
|
||||
let arrived = try #require(try pasted(destination).lanes.last)
|
||||
#expect(arrived.title.value == "Todo")
|
||||
// Both of the lane's cards.
|
||||
#expect(arrived.cards.count == 2)
|
||||
#expect(Set(arrived.cards.compactMap(\.title.value)) == ["First", "Second"])
|
||||
#expect(target.banners.losses.map(\.message) == ["Pasted 'Todo' without its 2 attachments"])
|
||||
#expect(try pasted(destination).lanes.map(\.id) == before, "the strip is untouched")
|
||||
let error = try #require(target.banners.oneShots.first).error
|
||||
#expect(BannerCenter.headline(for: error) == "Couldn't paste 'Todo' — the copied content is gone")
|
||||
}
|
||||
|
||||
@Test("A trash-sourced fallback materializes an ordinary card — there is no key to strip")
|
||||
func trashedFallbackIsOrdinary() async throws {
|
||||
/// **All-or-nothing for the whole paste** — the transactional half of the ruling, which the former
|
||||
/// mixed path is exactly what retired: one entry's snapshot going missing used to leave its
|
||||
/// siblings arriving whole beside a hollowed copy of it. Now the gesture refuses as a unit.
|
||||
@Test("One missing snapshot refuses the whole multi-entry paste")
|
||||
func oneMissingEntryRefusesTheWholePaste() async throws {
|
||||
let harness = try makeClipboardHarness()
|
||||
defer { harness.tearDown() }
|
||||
let destination = try makeDestination()
|
||||
defer { destination.tearDown() }
|
||||
let target = try BoardStore(rootURL: destination.root)
|
||||
|
||||
harness.store.transient.isTrashVisible = true
|
||||
harness.store.select([clipboardCard3], in: .trash)
|
||||
harness.store.select([clipboardCard1, clipboardCard2], in: .board)
|
||||
harness.clipboard.copy(from: harness.store)
|
||||
await harness.clipboard.stagingSettled()
|
||||
|
||||
// Only the *first* entry's tree is removed; the second is staged and perfectly pasteable.
|
||||
let copyID = try #require(harness.clipboard.payload?.copyID)
|
||||
try FileManager.default.removeItem(
|
||||
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
|
||||
at: harness.staging
|
||||
.appendingPathComponent(copyID, isDirectory: true)
|
||||
.appendingPathComponent(Ident.card1, isDirectory: true)
|
||||
)
|
||||
|
||||
target.select([destinationLane], in: .board)
|
||||
await harness.clipboard.paste(into: target)?.value
|
||||
|
||||
#expect(try pastedTitles(destinationLane, in: destination) == ["Resident", "Trashed"])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - BoardWriter.materializeItem
|
||||
|
||||
@Suite("BoardWriter ▸ materializeItem")
|
||||
struct MaterializeItemTests {
|
||||
|
||||
@Test("The supplied bytes land verbatim but for the rewritten order and stamps")
|
||||
func writesTheSuppliedBytes() throws {
|
||||
let fixture = try WriterFixture()
|
||||
defer { fixture.tearDown() }
|
||||
try fixture.item("", Item.board)
|
||||
try fixture.item(Ident.lane1, Item.rich(order: "1024", title: "Todo"))
|
||||
|
||||
let id = try BoardWriter.materializeItem(
|
||||
inParent: fixture.url(Ident.lane1),
|
||||
indexText: Item.rich(order: "9999", title: "Pasted"),
|
||||
order: 512
|
||||
#expect(
|
||||
try pastedTitles(destinationLane, in: destination) == ["Resident"],
|
||||
"not even the entry that could have arrived whole"
|
||||
)
|
||||
|
||||
let document = try FrontmatterDocument.parse(fixture.indexText("\(Ident.lane1)/\(id.rawValue)"))
|
||||
#expect(document.title.value == "Pasted")
|
||||
#expect(document.order.value == 512)
|
||||
#expect(document.value(for: "project") != nil)
|
||||
#expect(document.value(for: "labels") != nil)
|
||||
// The app-write stamps: `modified` set, `modified-by` cleared.
|
||||
#expect(document.modified.value != ISO8601DateFormatter().date(from: "2026-02-02T09:00:00Z"))
|
||||
#expect(document.modifiedBy.isMissing)
|
||||
// `created` untouched — a paste is a fork.
|
||||
#expect(document.created.value == ISO8601DateFormatter().date(from: "2026-01-01T09:00:00Z"))
|
||||
#expect(target.banners.oneShots.count == 1, "one refusal for one gesture")
|
||||
}
|
||||
|
||||
@Test("Children are materialized under fresh identities and never rewritten")
|
||||
func childrenAreVerbatim() throws {
|
||||
let fixture = try WriterFixture()
|
||||
defer { fixture.tearDown() }
|
||||
try fixture.item("", Item.board)
|
||||
|
||||
let id = try BoardWriter.materializeItem(
|
||||
inParent: fixture.root,
|
||||
indexText: Item.rich(order: "1024", title: "Lane"),
|
||||
children: [Item.rich(order: "1024", title: "One"), Item.uneditable],
|
||||
order: 1024
|
||||
)
|
||||
|
||||
let lane = try #require(try BoardLoader.load(boardRoot: fixture.root).model.lanes.first)
|
||||
#expect(lane.id == id)
|
||||
#expect(lane.cards.count == 2)
|
||||
// An uneditable child arrives exactly as it was — the leniency `copyItem` extends below its
|
||||
// root, applied here.
|
||||
let names = try FileManager.default.contentsOfDirectory(atPath: fixture.url(id.rawValue).path)
|
||||
.filter { $0 != "index.md" }
|
||||
let odd = try #require(names.first { name in
|
||||
(try? fixture.indexText("\(id.rawValue)/\(name)")) == Item.uneditable
|
||||
})
|
||||
#expect(try fixture.indexText("\(id.rawValue)/\(odd)") == Item.uneditable)
|
||||
/// **A refusal costs the user their content *and* nothing else** — the destination's active search
|
||||
/// survives it. "Any user-initiated creation on the board clears the query" (04 ▸ Search) is a rule
|
||||
/// about creations, and a refused paste creates nothing.
|
||||
@Test("A refused paste leaves the destination's search alone")
|
||||
func aRefusalKeepsTheSearch() async throws {
|
||||
let harness = try makeClipboardHarness()
|
||||
defer { harness.tearDown() }
|
||||
let destination = try makeDestination()
|
||||
defer { destination.tearDown() }
|
||||
let target = try BoardStore(rootURL: destination.root)
|
||||
target.transient.searchQuery = "resident"
|
||||
|
||||
harness.store.select([clipboardCard1], in: .board)
|
||||
harness.clipboard.copy(from: harness.store)
|
||||
await harness.clipboard.stagingSettled()
|
||||
try loseTheSnapshot(harness)
|
||||
|
||||
target.select([destinationLane], in: .board)
|
||||
await harness.clipboard.paste(into: target)?.value
|
||||
|
||||
#expect(target.transient.searchQuery == "resident")
|
||||
}
|
||||
|
||||
@Test("An unparseable root refuses and leaves nothing behind")
|
||||
func unparseableRootLeavesNoResidue() throws {
|
||||
let fixture = try WriterFixture()
|
||||
defer { fixture.tearDown() }
|
||||
try fixture.item("", Item.board)
|
||||
|
||||
let error = writeFailure {
|
||||
_ = try BoardWriter.materializeItem(
|
||||
inParent: fixture.root,
|
||||
indexText: "no frontmatter here at all\n",
|
||||
order: 1024
|
||||
)
|
||||
}
|
||||
#expect(error != nil)
|
||||
#expect(try fixture.entryNames("") == ["index.md"])
|
||||
/// A cut whose staged snapshot is gone is a different story and stays one: an armed cut moves the
|
||||
/// **originals**, which are real folders in the source board, so it never reads staging at all.
|
||||
/// The refusal is the copy path's, and this pins that it did not spread.
|
||||
@Test("An armed cut still moves its originals — it never reads staging")
|
||||
func anArmedCutIsUnaffected() async throws {
|
||||
let harness = try makeClipboardHarness()
|
||||
defer { harness.tearDown() }
|
||||
let destination = try makeDestination()
|
||||
defer { destination.tearDown() }
|
||||
let target = try BoardStore(rootURL: destination.root)
|
||||
|
||||
harness.store.select([clipboardCard1], in: .board)
|
||||
harness.clipboard.cut(from: harness.store)
|
||||
await harness.clipboard.stagingSettled()
|
||||
try loseTheSnapshot(harness)
|
||||
|
||||
target.select([destinationLane], in: .board)
|
||||
await harness.clipboard.paste(into: target)?.value
|
||||
|
||||
#expect(try pastedTitles(destinationLane, in: destination) == ["Resident", "First"])
|
||||
#expect(target.banners.oneShots.isEmpty, "nothing failed — the folder moved")
|
||||
#expect(harness.fixture.exists("\(Ident.lane1)/\(Ident.card1)") == false, "and it left the source")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user