The board's symbol takes a tint — a 4×2 colour row under the picker's glyphs, and the glyph itself moves into the titlebar
iconColor stops being hand-written-only (user-ruled, superseding 03's "schema yes, control no"): it rides StyleCommand.apply → applyStyle as the third styled dimension — per-dimension no-op skip, one bracket, one history step, ExpectedField.iconColor for staleness. The SymbolPicker grows an opt-in colour row (leading None plus seven Palette.foregrounds hues, None removes the key); the board popover is its one caller. The window-title widget now draws the board's resolved glyph in that tint beside the name. Doc realignment filed on the Redesign board (Minor). Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -33,7 +33,8 @@ private func styled(order: String, title: String, keys: [String] = []) -> String
|
||||
}
|
||||
|
||||
/// The board root's own `index.md` — styled like any other item (`StyleTarget.board`), and carrying
|
||||
/// an `iconColor` the app must never touch (schema yes, control no).
|
||||
/// an `iconColor` a gesture that never names that dimension must leave alone (it stopped being
|
||||
/// hand-written-only when the board popover's symbol picker grew its colour row, 2026-08-07).
|
||||
private let boardIndex = """
|
||||
---
|
||||
schema: 1
|
||||
@@ -143,13 +144,38 @@ struct StyleWriteTests {
|
||||
let after = try fixture.indexText("\(Ident.lane1)/\(Ident.card1)")
|
||||
#expect(after.contains("background: {color: \"dark-teal\"}"))
|
||||
#expect(after.contains("icon: flag"))
|
||||
// "iconColor: resolved — schema yes, control no" (03 § Styling ▸ Capabilities): the field
|
||||
// renders when hand-written and the app offers no control for it, so a style write must
|
||||
// carry it through untouched like any unknown key.
|
||||
// The third dimension is a control now (the symbol picker's colour row), but a gesture
|
||||
// that never names it must still carry it through untouched — per-dimension `.keep`.
|
||||
#expect(after.contains("iconColor: chalk"))
|
||||
#expect(!after.contains("fern"), "the old value is replaced, not duplicated")
|
||||
}
|
||||
|
||||
@Test("The colour row's dimension: iconColor sets as a plain scalar, removes as an absent key, and no-ops in place")
|
||||
func iconColorIsTheThirdDimension() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let log = BracketLog()
|
||||
log.attach(to: store)
|
||||
|
||||
// The fixture's board already carries `iconColor: carnation` — re-choosing it is a no-op
|
||||
// against the loaded snapshot, `skipsNoOps`'s rule at the third dimension.
|
||||
store.applyStyle(to: .board, iconColor: .set("carnation"))
|
||||
#expect(log.begins == 0, "re-choosing the tint an item already carries opens no bracket")
|
||||
|
||||
store.applyStyle(to: .board, iconColor: .set("fern"))
|
||||
let set = try fixture.indexText("")
|
||||
#expect(set.contains("iconColor: fern"))
|
||||
#expect(!set.contains("carnation"), "the old tint is replaced, not duplicated")
|
||||
#expect(!set.contains("icon: "), "the untouched icon dimension writes no key of its own")
|
||||
|
||||
store.applyStyle(to: .board, iconColor: .remove)
|
||||
let removed = try fixture.indexText("")
|
||||
#expect(!removed.contains("iconColor"))
|
||||
#expect(!removed.contains("\"\""), "a removal is a missing key, never an empty string")
|
||||
#expect(store.banners.oneShots.isEmpty)
|
||||
}
|
||||
|
||||
@Test("The None and default wells remove their key rather than writing a blank value")
|
||||
func removesTheKey() throws {
|
||||
let fixture = try makeBoard()
|
||||
|
||||
@@ -23,6 +23,23 @@ struct SymbolPickerCatalogDefaultSetTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Suite("SymbolPicker ▸ the colour row")
|
||||
struct SymbolPickerColorSetTests {
|
||||
|
||||
@Test("Seven unique tints — the leading None plus these fills the 4×2 grid exactly")
|
||||
func shape() {
|
||||
#expect(SymbolPickerCatalog.colorSet.count
|
||||
== SymbolPickerLayout.colorColumns * SymbolPickerLayout.colorRows - 1)
|
||||
#expect(Set(SymbolPickerCatalog.colorSet).count == SymbolPickerCatalog.colorSet.count)
|
||||
}
|
||||
|
||||
@Test("Every entry is a palette name that resolves — pins the list against typos and palette drift")
|
||||
func everyNameResolves() {
|
||||
let missing = SymbolPickerCatalog.colorSet.filter { Palette.color(named: $0) == nil }
|
||||
#expect(missing.isEmpty, "unresolvable palette names: \(missing)")
|
||||
}
|
||||
}
|
||||
|
||||
@Suite("SymbolPicker ▸ filter")
|
||||
struct SymbolPickerFilterTests {
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ order: 3072
|
||||
project: lanework # agent overlay
|
||||
background: {color: blue}
|
||||
icon: star
|
||||
iconColor: chalk
|
||||
created: 2026-01-01T09:00:00Z
|
||||
---
|
||||
Styled body.
|
||||
@@ -249,6 +250,27 @@ struct RestyleUndoTests {
|
||||
let undone = try document(fixture, card3Path)
|
||||
#expect(undone.background.value == "blue")
|
||||
#expect(undone.icon.value == "star", "a dimension the gesture did not touch is not touched back")
|
||||
#expect(undone.iconColor.value == "chalk", "the third dimension rides the same rule")
|
||||
}
|
||||
|
||||
@Test("The colour row's dimension round-trips: a tint undoes to the prior tint, and to absence where there was none")
|
||||
func iconColorRoundTrip() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let (store, history) = try makeStore(fixture)
|
||||
|
||||
// card3 carries `iconColor: chalk`; card1 carries none.
|
||||
store.applyStyle(to: .items([card3]), iconColor: .set("fern"))
|
||||
#expect(try document(fixture, card3Path).iconColor.value == "fern")
|
||||
|
||||
history.undo()
|
||||
#expect(try document(fixture, card3Path).iconColor.value == "chalk")
|
||||
history.redo()
|
||||
#expect(try document(fixture, card3Path).iconColor.value == "fern")
|
||||
|
||||
store.applyStyle(to: .items([card1]), iconColor: .set("carnation"))
|
||||
history.undo()
|
||||
#expect(try document(fixture, card1Path).iconColor.isMissing, "no prior tint undoes to no key at all")
|
||||
}
|
||||
|
||||
@Test("A styling batch is one step, with a plural title")
|
||||
|
||||
Reference in New Issue
Block a user