The board wears a picture — background becomes a mapping, and the window chrome follows it under a thin frost
background is {color:, image:} and only a mapping at every level; the board's image paints the full window under a transparent title bar, with a thin-material frost strip keeping the chrome legible and the standard accommodations intact.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -551,8 +551,8 @@ struct FrontmatterLenientFieldTests {
|
||||
|
||||
@Test func wellFormedLenientValues() throws {
|
||||
#expect(try document("title: My Board").title == .valid("My Board"))
|
||||
#expect(try document("background: \"#ff8800\"").background == .valid("#ff8800"))
|
||||
#expect(try document("background: slate").background == .valid("slate"))
|
||||
#expect(try document("background: {color: \"#ff8800\"}").background == .valid("#ff8800"))
|
||||
#expect(try document("background: {color: slate}").background == .valid("slate"))
|
||||
#expect(try document("icon: tray.full").icon == .valid("tray.full"))
|
||||
#expect(try document("iconColor: teal").iconColor == .valid("teal"))
|
||||
#expect(try document("width: 3").width == .valid(3))
|
||||
@@ -565,7 +565,7 @@ struct FrontmatterLenientFieldTests {
|
||||
@Test func scalarsOfTheWrongTypeCoerceToTheirSourceText() throws {
|
||||
#expect(try document("title: 2048").title == .valid("2048"))
|
||||
#expect(try document("title: true").title == .valid("true"))
|
||||
#expect(try document("background: 42").background == .valid("42"))
|
||||
#expect(try document("background: {color: 42}").background == .valid("42"))
|
||||
#expect(try document("iconColor: true").iconColor == .valid("true"))
|
||||
#expect(try document("icon: 2026-07-26T16:41:38Z").icon == .valid("2026-07-26T16:41:38Z"))
|
||||
}
|
||||
@@ -575,7 +575,7 @@ struct FrontmatterLenientFieldTests {
|
||||
@Test func aTrailingCommentIsNotPartOfACoercedValue() throws {
|
||||
#expect(try document("title: 2048 # note").title == .valid("2048"))
|
||||
#expect(try document("title: true # note").title == .valid("true"))
|
||||
#expect(try document("background: 42\t# tabbed").background == .valid("42"))
|
||||
#expect(try document("background: {color: 42}\t# tabbed").background == .valid("42"))
|
||||
#expect(try document("icon: 2026-07-26T16:41:38Z # when").icon == .valid("2026-07-26T16:41:38Z"))
|
||||
}
|
||||
|
||||
@@ -583,8 +583,8 @@ struct FrontmatterLenientFieldTests {
|
||||
/// at all, so a read must not treat it as a comment.
|
||||
@Test func aHashInsideAQuotedValueIsNotTrimmed() throws {
|
||||
#expect(try document("title: \"2048 # note\"").title == .valid("2048 # note"))
|
||||
#expect(try document("background: \"#ff8800\"").background == .valid("#ff8800"))
|
||||
#expect(try document("background: \"#ff8800\" # brand orange").background == .valid("#ff8800"))
|
||||
#expect(try document("background: {color: \"#ff8800\"}").background == .valid("#ff8800"))
|
||||
#expect(try document("background: {color: \"#ff8800\"} # brand orange").background == .valid("#ff8800"))
|
||||
}
|
||||
|
||||
/// A sequence or mapping has no scalar reading at all; the raw text it falls back to stops at
|
||||
@@ -608,6 +608,115 @@ struct FrontmatterLenientFieldTests {
|
||||
#expect(try document("title: [a, b]").title == .malformed(raw: "[a, b]"))
|
||||
}
|
||||
|
||||
// MARK: `background` is a mapping and only a mapping
|
||||
|
||||
/// **The retired scalar** (01-storage-format.md § Frontmatter, ruled 2026-08-06 before anything
|
||||
/// shipped — one shape, no legacy spelling, no migration): `background: green` has no reading at
|
||||
/// all. It is `.malformed` like any other unreadable value, which renders as no colour and
|
||||
/// leaves the bytes exactly as written.
|
||||
@Test func aScalarBackgroundHasNoReading() throws {
|
||||
#expect(try document("background: green").background == .malformed(raw: "green"))
|
||||
#expect(try document("background: \"#ff8800\"").background == .malformed(raw: "\"#ff8800\""))
|
||||
#expect(try document("background: 42").background == .malformed(raw: "42"))
|
||||
// The raw stops at the comment like every other read, since a comment is the line's.
|
||||
#expect(try document("background: green # my colour").background == .malformed(raw: "green"))
|
||||
}
|
||||
|
||||
/// One unreadable value is reported **once**: the colour reading owns the key's shape, and the
|
||||
/// image stays silent about a file that never wrote a mapping to name a picture in.
|
||||
@Test func onlyTheColourReportsANonMappingShape() throws {
|
||||
#expect(try document("background: green").backgroundImage == .missing)
|
||||
#expect(try document("background: [red, blue]").backgroundImage == .missing)
|
||||
#expect(try document("background: 42").backgroundImage == .missing)
|
||||
#expect(try document("schema: 1").backgroundImage == .missing)
|
||||
#expect(try document("background: green").coercedFields
|
||||
== [CoercedField(key: "background", raw: "green")])
|
||||
}
|
||||
|
||||
/// The mapping form, both halves present — flow and block spellings are one YAML value and
|
||||
/// therefore one reading.
|
||||
@Test func aMappingBackgroundReadsBothSubkeys() throws {
|
||||
let flow = try document("background: {color: \"#112233\", image: sunset.jpg}")
|
||||
#expect(flow.background == .valid("#112233"))
|
||||
#expect(flow.backgroundImage == .valid("sunset.jpg"))
|
||||
|
||||
let block = try FrontmatterDocument.parse(
|
||||
"---\nbackground:\n color: fern\n image: art/sunset.jpg\n---\nbody\n"
|
||||
)
|
||||
#expect(block.background == .valid("fern"))
|
||||
#expect(block.backgroundImage == .valid("art/sunset.jpg"))
|
||||
}
|
||||
|
||||
/// Either half may be absent, and an absent half is `.missing` — not malformed. An image-only
|
||||
/// background is a board with no colour, which is the level's default and not a fallback.
|
||||
@Test func eitherSubkeyMayBeAbsent() throws {
|
||||
let colorOnly = try document("background: {color: chalk}")
|
||||
#expect(colorOnly.background == .valid("chalk"))
|
||||
#expect(colorOnly.backgroundImage == .missing)
|
||||
|
||||
let imageOnly = try document("background: {image: sunset.jpg}")
|
||||
#expect(imageOnly.background == .missing)
|
||||
#expect(imageOnly.backgroundImage == .valid("sunset.jpg"))
|
||||
|
||||
let empty = try document("background: {}")
|
||||
#expect(empty.background == .missing)
|
||||
#expect(empty.backgroundImage == .missing)
|
||||
}
|
||||
|
||||
/// An explicit null subkey reads exactly like an absent one — `FieldValue.missing` already
|
||||
/// treats `background: null` that way, and a subkey is no different.
|
||||
@Test func nullSubkeysReadAsMissing() throws {
|
||||
let nulls = try document("background: {color: null, image: ~}")
|
||||
#expect(nulls.background == .missing)
|
||||
#expect(nulls.backgroundImage == .missing)
|
||||
}
|
||||
|
||||
/// Unknown subkeys are tolerated on the way in exactly as unknown *keys* are — they mean
|
||||
/// nothing to either reading and cost it nothing.
|
||||
@Test func unknownSubkeysAreTolerated() throws {
|
||||
let extra = try document("background: {opacity: 0.5, color: fern, blend: multiply}")
|
||||
#expect(extra.background == .valid("fern"))
|
||||
#expect(extra.backgroundImage == .missing)
|
||||
}
|
||||
|
||||
/// Inside the mapping the subvalues coerce like any other scalar, quoted or not; a subvalue with
|
||||
/// no scalar reading at all is malformed, quoting the subvalue rather than the whole span —
|
||||
/// a subkey has no source span of its own to quote.
|
||||
@Test func subkeyScalarsCoerceAndCollectionsAreMalformed() throws {
|
||||
#expect(try document("background: {color: 42}").background == .valid("42"))
|
||||
#expect(try document("background: {image: \"sun set.jpg\"}").backgroundImage == .valid("sun set.jpg"))
|
||||
#expect(try document("background: {color: [a, b]}").background == .malformed(raw: "[a, b]"))
|
||||
#expect(try document("background: {image: {a: 1}}").backgroundImage == .malformed(raw: "{a: 1}"))
|
||||
// The other half of a mapping with one bad subkey still reads perfectly well.
|
||||
#expect(try document("background: {color: [a, b], image: sunset.jpg}").backgroundImage == .valid("sunset.jpg"))
|
||||
}
|
||||
|
||||
/// **The coerce tier's trace covers both readings** (01-storage-format.md § Frontmatter: "every
|
||||
/// silent recovery leaves a trace"). Both are filed under the key the schema spells, and are told
|
||||
/// apart by the subvalue each quotes — so a mapping whose colour reads fine and whose image does
|
||||
/// not is still visible.
|
||||
@Test func bothBackgroundReadingsReachTheCoerceRecord() throws {
|
||||
#expect(try document("background: {color: fern, image: [a, b]}").coercedFields
|
||||
== [CoercedField(key: "background", raw: "[a, b]")])
|
||||
#expect(try document("background: {color: [a], image: [b]}").coercedFields
|
||||
== [CoercedField(key: "background", raw: "[a]"), CoercedField(key: "background", raw: "[b]")])
|
||||
// A sequence is one unreadable value and is reported once — the image reading stays silent
|
||||
// about a shape that never claimed to name one.
|
||||
#expect(try document("background: [red, blue]").coercedFields
|
||||
== [CoercedField(key: "background", raw: "[red, blue]")])
|
||||
#expect(try document("background: {image: sunset.jpg}").coercedFields.isEmpty)
|
||||
}
|
||||
|
||||
/// The engine reads the shape; it never rewrites it. A mapping background round-trips
|
||||
/// byte-identically like every other value the app did not touch.
|
||||
@Test func aMappingBackgroundRoundTripsVerbatim() throws {
|
||||
let text = "---\nschema: 1\nbackground:\n color: fern\n image: art/sunset.jpg\n blend: multiply\n---\nbody\n"
|
||||
let document = try FrontmatterDocument.parse(text)
|
||||
#expect(document.serialized() == text)
|
||||
#expect(document.background == .valid("fern"))
|
||||
#expect(document.backgroundImage == .valid("art/sunset.jpg"))
|
||||
}
|
||||
|
||||
/// Only a fractional or non-numeric reading has no sensible width at all — a sequence,
|
||||
/// mapping, or scalar with no integer reading whatsoever stays malformed and renders as the
|
||||
/// default 1.
|
||||
|
||||
Reference in New Issue
Block a user