Add the loss-row banner class and retone degraded paste

02 ratified a warning-tone class for non-failure losses — content that
didn't arrive though nothing failed: a degraded paste, folders skipped
from a Finder drop, their future kin. Loss rows take the one-shot's
dismissable-untimed lifecycle (a loss the user didn't notice is the
harm) and rank below the true failures, above commit and attachment
notices. BannerCenter grows LossBanner, postLoss, and the
skipped-folders phrasing; the degraded-paste notice moves off its
signpost onto the new class, ending its too-quiet ranking.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-28 07:17:27 -04:00
parent 15006ad233
commit 756e936291
6 changed files with 312 additions and 46 deletions
+3 -3
View File
@@ -494,7 +494,7 @@ struct PasteFallbackTests {
target.select([destinationLane], liveness: .live)
await harness.clipboard.paste(into: target)?.value
#expect(target.banners.signposts.map(\.message) == ["Pasted 'First' without its 2 attachments"])
#expect(target.banners.losses.map(\.message) == ["Pasted 'First' without its 2 attachments"])
}
@Test("A fallback that lost nothing says nothing")
@@ -518,7 +518,7 @@ struct PasteFallbackTests {
await harness.clipboard.paste(into: target)?.value
#expect(try pastedTitles(destinationLane, in: destination) == ["Resident", "Second"])
#expect(target.banners.signposts.isEmpty)
#expect(target.banners.losses.isEmpty)
}
@Test("A lane's fallback materializes its embedded cards")
@@ -544,7 +544,7 @@ struct PasteFallbackTests {
// The two live cards, and not the tombstoned third.
#expect(arrived.cards.count == 2)
#expect(Set(arrived.cards.compactMap(\.title.value)) == ["First", "Second"])
#expect(target.banners.signposts.map(\.message) == ["Pasted 'Todo' without its 2 attachments"])
#expect(target.banners.losses.map(\.message) == ["Pasted 'Todo' without its 2 attachments"])
}
@Test("A trash-sourced fallback still strips `deleted:` at materialization")