Orphaned .backgrounds/ files get tidied — a repoint trims the one it leaves behind, and every open sweeps what got away

Follow-up to d0c5461's .backgrounds/ folder: generating, pasting, or
choosing solid now trims the app's own prior file in .backgrounds/ as
part of the same write when it repoints or unsets background.image
away from it — silent, best-effort, never blocking the gesture that
triggered it. A scheduled heal at every board open sweeps whatever
that trim declined or missed: any .backgrounds/ file the board's
current background.image no longer names, announced with a loss-row
notice in the loose-file relocation's own voice. Legacy root-level
references are untouched by both paths — the tidy scopes to
.backgrounds/ only, since that is the one folder the app can prove it
wrote into.

Removal is via FileManager.trashItem, matching the attachment-removal
precedent (recoverable, never a hard delete).

Supersedes applySolidBackground's earlier "facets.png survives on
disk" contract for the settled case: the ruling reads "unsets" as one
more shape of "repoints away from a .backgrounds/ file", so a settled
solid choice now trims the generated picture it displaces, same as
switching producers does. The superseded test and doc comment are
updated to the new behavior; the echo-window (no-reload-yet) case is
unaffected and still leaves the file in place.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-09 09:44:30 -04:00
parent d905e73960
commit ca0328be2e
9 changed files with 858 additions and 26 deletions
+7
View File
@@ -171,6 +171,11 @@ public final class HealScheduler {
/// This row is what replaced the retired user-gated Repair banner: the same information, none
/// of the waiting.
case remintedDuplicateIDs(titles: [String?])
/// Loss row, warning tone: `.backgrounds/` files nothing on the board named any more were
/// moved to the system Trash, named by filename (ruled 2026-08-09 the open-time heal half
/// of the orphan tidy; the in-flow trim rides silently inside the gesture that triggers it and
/// posts nothing here).
case tidiedBackgroundImages([String])
}
private static func post(_ notice: HealNotice, to banners: BannerCenter) {
@@ -185,6 +190,8 @@ public final class HealScheduler {
banners.postDisplacedClaimedNames(displacements)
case let .remintedDuplicateIDs(titles):
banners.postRemintedDuplicateIDs(titles)
case let .tidiedBackgroundImages(names):
banners.postTidiedBackgroundImages(names)
}
}