The Background tab fills in — facets rendered to order, eight hues in a carousel

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-08-07 16:08:29 -04:00
parent 56e37be158
commit fb96e30df0
19 changed files with 3031 additions and 38 deletions
+138
View File
@@ -173,6 +173,144 @@ struct BackgroundWriteTests {
}
}
// MARK: - Writing the image subkey
/// `FrontmatterDocument.setBackgroundImage` the colour write's sibling, one subkey over
/// (BackgroundField.swift), and what `BoardStore.applyGeneratedBackground` points at the PNG it just
/// wrote. Every rule the colour write obeys, this one obeys too: that is the whole reason they share
/// a merge.
@Suite("Board background ▸ the image subkey")
struct BackgroundImageWriteTests {
/// The mirror of `setKeepsTheImage`: the app now writes both subkeys, and neither may take the
/// other with it.
@Test("Setting an image replaces the subkey and keeps the colour")
func setKeepsTheColour() throws {
var document = try document("background: {color: fern, image: sunset.jpg}")
document.setBackgroundImage("facets.png")
#expect(document.background == .valid("fern"))
#expect(document.backgroundImage == .valid("facets.png"))
#expect(backgroundLine(document) == "background: {color: \"fern\", image: \"facets.png\"}")
}
/// A colour-only board every board styled from the wells is the one the generator is most
/// likely to be pointed at.
@Test("Setting an image on a colour-only background adds the subkey")
func setAddsTheSubkeyToAColourOnlyMapping() throws {
var document = try document("background: {color: fern}")
document.setBackgroundImage("facets.png")
#expect(backgroundLine(document) == "background: {color: \"fern\", image: \"facets.png\"}")
}
/// Unknown subkeys ride along here exactly as they do through a colour change the merge is
/// literally the same one.
@Test("Unknown subkeys survive an image change, in their own positions")
func setPreservesUnknownSubkeys() throws {
var document = try document("background: {blend: multiply, image: old.png, opacity: 0.5}")
document.setBackgroundImage("facets.png")
#expect(backgroundLine(document)
== "background: {blend: \"multiply\", image: \"facets.png\", opacity: 0.5}")
}
/// The undo of a first generation: the image subkey goes and the colour the board had or did
/// not have is left to the colour write.
@Test("Removing the image drops that subkey alone")
func removeDropsOnlyTheImage() throws {
var document = try document("background: {color: fern, image: facets.png}")
document.setBackgroundImage(nil)
#expect(document.background == .valid("fern"))
#expect(document.backgroundImage == .missing)
#expect(backgroundLine(document) == "background: {color: \"fern\"}")
}
/// `background: {}` is a key that says nothing the removal's contract is that the field is
/// gone, whichever subkey emptied it.
@Test("A mapping emptied by the removal takes the key with it")
func removeDropsAnEmptiedKey() throws {
var document = try document("background: {image: facets.png}")
document.setBackgroundImage(nil)
#expect(!document.contains(FrontmatterKeys.background))
#expect(backgroundLine(document) == nil)
}
/// The malformed-value-cleared posture, on this subkey: a shape the schema cannot read has no
/// subkeys to preserve and is replaced by the mapping the app writes.
@Test("An image written onto an absent or unreadable key lands as a mapping")
func alwaysWritesTheMapping() throws {
var absent = try document("schema: 1")
absent.setBackgroundImage("facets.png")
#expect(backgroundLine(absent) == "background: {image: \"facets.png\"}")
var scalar = try document("background: fern")
scalar.setBackgroundImage("facets.png")
#expect(backgroundLine(scalar) == "background: {image: \"facets.png\"}")
var sequence = try document("background: [a, b]")
sequence.setBackgroundImage("facets.png")
#expect(backgroundLine(sequence) == "background: {image: \"facets.png\"}")
}
/// Both subkeys written in one edit, which is the shape every generated background lands in
/// and the order the schema spells it in, colour first.
@Test("A colour and an image written together land as one mapping")
func bothSubkeysTogether() throws {
var document = try document("schema: 1")
document.setStyleValue("#E0E5EB", for: FrontmatterKeys.background)
document.setBackgroundImage("facets.png")
#expect(backgroundLine(document) == "background: {color: \"#E0E5EB\", image: \"facets.png\"}")
#expect(document.background == .valid("#E0E5EB"))
#expect(document.backgroundImage == .valid("facets.png"))
}
/// Everything outside the key survives byte for byte, including the comment on the unknown key
/// beside it the verbatim promise, which yields on the one key being rewritten and nowhere else.
@Test("Nothing but the background line moves")
func leavesEverythingElseAlone() throws {
var document = try FrontmatterDocument.parse("""
---
schema: 1
title: Work
project: lanework # agent overlay
background: {color: fern}
icon: tray
---
Board description.
""")
document.setBackgroundImage("facets.png")
#expect(document.serialized() == """
---
schema: 1
title: Work
project: lanework # agent overlay
background: {color: "fern", image: "facets.png"}
icon: tray
---
Board description.
""")
}
/// The emitted mapping is read back by the reader the app uses a name with YAML-significant
/// characters in it included, which is the reason strings are always quoted in flow context.
@Test("An awkward file name round-trips through the emitted mapping")
func awkwardNamesRoundTrip() throws {
var document = try document("background: {color: fern}")
document.setBackgroundImage("a, b}.png")
let reparsed = try FrontmatterDocument.parse(document.serialized())
#expect(reparsed.backgroundImage == .valid("a, b}.png"))
#expect(reparsed.background == .valid("fern"))
}
}
// MARK: - Where an image may point
@Suite("Board background ▸ the image path stays inside the board")