replace symbol grid in card window sidebar with a compact symbol picker

The card window sidebar's Style section embedded StyleEditorView symbols-only
(showsBackground: false, showsSymbols: true), drawing the curated grid whole
inside the sidebar's own scroll view — a permanently open 5-7 row grid ahead
of every other section. Replace it with the reusable compact SymbolPicker
(already used by BoardInfoPopover for the board's glyph): a single well at
rest, the grid only inside its own popover.

- CardStyleSection no longer instantiates StyleEditorView at all. A new
  "Symbol" row mirrors the existing "Background" row's inspector shape
  (caption leading, control trailing), wiring SymbolPicker's onSelect/
  onSelectColor to icon/iconColor through StyleCommand.apply(...on: undo),
  the exact funnel the background combo already rides — so the card window's
  undo session semantics (13-native-undo.md) are unchanged.
- The picker is fed CuratedSymbols.available rather than its own smaller
  general-purpose default, so a card's curated vocabulary doesn't shrink.
- StyleEditorLayout.sidebar and showsBackground stay in StyleEditor.swift
  (still correct, still tested) rather than being cut as dead code — a
  separate, larger cleanup this card doesn't make (recorded on the card).
- DESIGN/03-board-ui.md and DESIGN/05-card-window.md: updated the sentences
  describing the sidebar hosting the style editor's symbol grid to describe
  the compact picker instead.
- Tests: CardSessionUndoTests gains a symbol/tint analogue of the existing
  background-combo session-routing test; CardSidebarTests gains a curated-set
  tripwire and an updated file-header note.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-08 22:14:40 -04:00
parent 898facebe0
commit 05ae703989
6 changed files with 216 additions and 44 deletions
+23 -3
View File
@@ -11,11 +11,16 @@ import Testing
/// - **Details** shows frontmatter the app deliberately does not understand, so nothing downstream
/// can notice when a key goes missing, arrives out of order, or renders as a YAML indicator. The
/// only check on it is a test that reads a file and says what the rows must be.
/// - **Style** embeds a shared, selection-aware component in a window that has no selection. An
/// anchor wired to the wrong target would look completely normal until it restyled something else.
/// - **Style** pins its own target this window's card, always, never the selection-aware target the
/// Style popover carries and (2026-08-08) hands the compact `SymbolPicker` the kanban-relevant
/// `CuratedSymbols` set rather than the picker's own smaller general-purpose default. A target wired
/// to the wrong card, or a curated list quietly narrowed back to the picker's default, would look
/// completely normal until it restyled the wrong card or hid a symbol the vocabulary used to offer.
/// - **The sidebar's geometry** is a grid of fixed-size wells in a column sized from font metrics:
/// at the wrong column count the last well in each row is simply unreachable, at no text size
/// anyone tests by eye.
/// anyone tests by eye. (The `StyleEditorLayout.sidebar` variant below now describes geometry no
/// view in the app currently requests the Style section's grid moved into `SymbolPicker`'s own
/// popover on 2026-08-08 but the pure function is still correct and still worth pinning.)
// MARK: - Details which keys
@@ -328,6 +333,21 @@ struct CardStyleAnchorTests {
let upper = ItemID(rawValue: Ident.card1.uppercased())
#expect(CardStyleSection.target(forCard: lower) == CardStyleSection.target(forCard: upper))
}
/// **The sidebar's `SymbolPicker` is fed `CuratedSymbols.available`, not the picker's own
/// smaller default** the design ruling behind the 2026-08-08 grid-to-picker swap ("so a card's
/// curated vocabulary doesn't shrink"). Not a test *of* the view (this file's own stance), but of
/// the two catalogs the ruling is a claim about: if `CuratedSymbols` ever shrank to
/// `SymbolPickerCatalog.defaultSet`'s size or below, the sidebar's explicit `symbols:` argument
/// would have quietly become a no-op, and this is the tripwire for that.
@Test("The card sidebar's curated symbol vocabulary is at least as wide as the picker's own default")
func theCuratedSetPassedToTheSidebarPickerDoesNotShrinkIt() {
#expect(CuratedSymbols.available.count > SymbolPickerCatalog.defaultSet.count)
// And it is still the same *available* set every other curated surface reads filtered
// through `ItemSymbol.exists` (`CuratedSymbols.available`'s own doc comment), never the
// unfiltered `all`, so the sidebar never offers a well this Mac cannot draw.
#expect(CuratedSymbols.available.allSatisfy(ItemSymbol.exists))
}
}
// MARK: - Style the anchor's geometry