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:
2026-08-07 18:36:49 -04:00
parent 3d231d6454
commit 7ac34651a2
12 changed files with 342 additions and 23 deletions
+33
View File
@@ -115,6 +115,18 @@ struct BoardInfoWidget: View {
presentation.toggle()
} label: {
HStack(spacing: 4) {
// The board's own glyph beside its name the identity pair the popover header
// states, restated where the board is named all day (2026-08-07). Read inside
// `body` for `summary`'s reason: `icon`/`iconColor` are `@Observable` fields, so a
// restyle from the popover repaints this widget without reinstalling it. Lenient on
// both dimensions an unresolvable glyph draws the board default, an unresolvable
// tint draws the standard secondary.
Image(systemName: ItemSymbol.name(store.snapshot.icon, fallback: ItemSymbol.board))
.imageScale(.small)
.foregroundStyle(iconTint)
// Decorative beside the name it repeats the button's own label already says
// everything VoiceOver needs (`accessibilityLabel` below).
.accessibilityHidden(true)
Text(summary.title)
// Styled like a titlebar title, because that is what it now stands in for
// (`BoardWindowHost` hides the system title display in favor of this widget).
@@ -172,6 +184,15 @@ struct BoardInfoWidget: View {
guard let branch = summary.branch else { return summary.title }
return "\(summary.title), branch \(branch)"
}
/// The widget glyph's tint: the board's `iconColor` where it resolves, the quiet secondary
/// otherwise `CardFaceView`'s `iconTint` rule at the board's own level.
private var iconTint: AnyShapeStyle {
if let color = Palette.color(for: store.snapshot.iconColor) {
return AnyShapeStyle(color)
}
return AnyShapeStyle(.secondary)
}
}
// MARK: - The widget's strings
@@ -352,6 +373,18 @@ struct BoardInfoView: View {
in: store,
recents: recents
)
},
// The colour row the picker's 4×2 tint grid, writing `iconColor` through
// the same funnel the glyph writes `icon`: None removes the key, a well
// writes the palette name (2026-08-07).
currentColor: store.snapshot.iconColor.value,
onSelectColor: { name in
StyleCommand.apply(
iconColor: name.map { StyleChange.set($0) } ?? .remove,
to: .board,
in: store,
recents: recents
)
}
)
.disabled(!store.acceptsBoardMutations)