Build the styling system and shared style editor
One style-editor component, anchor-agnostic: a background grid (None well plus the 12 palette colors) and a curated symbol grid (the pathfinder's five-dozen set, leading well removing the icon key for the level default), selection-aware across cards, lanes, and the board itself. Batch edits compute per-dimension state — uniform, mixed (no well selected), or an off-palette value labeled verbatim outside the grids — and choosing a well applies to the whole target set as one write bracket, skipping no-ops per field. The popover tracks its target set live per the freshly ratified rule: targets re-resolve by UUID on every reload, a vanished target leaves the set, an emptied set dismisses the editor, and nothing ever silently retargets to the board. Anchors landing now: Board > Style (Opt-Cmd-S) and the card/lane context menus, which also carry the quick-style recents row (app-wide, persisted, capped at six, None never recorded) and the lane's width control twinning the menu chords. The styling system's other two renders arrive with it: a lane's background paints the C7 top-edge band, the board's paints the window content background — malformed values paint nothing and stay byte-identical on disk. 31 new tests. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -45,6 +45,10 @@ struct BoardView: View {
|
||||
/// needs the board's own window ref, which is the host's identity and not the board's.
|
||||
let openCard: (ItemID) -> Void
|
||||
|
||||
/// The app-wide quick-style recents, for the board-anchored style editor (03-board-ui.md §
|
||||
/// Styling ▸ Controls).
|
||||
@Environment(AppModel.self) private var appModel
|
||||
|
||||
/// One resize at a time, per window. `@State` so it lives exactly as long as this board window's
|
||||
/// view does, which is the interaction's whole lifetime.
|
||||
@State private var resize = LaneResizeSession()
|
||||
@@ -89,6 +93,12 @@ struct BoardView: View {
|
||||
.padding(spacing)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
}
|
||||
.background(boardBackground)
|
||||
// The board's own anchor for the Style… popover — the surface a board-targeted session hangs
|
||||
// off, since the board has no item to attach to (`styleEditorPresentation`'s `nil` anchor).
|
||||
.popover(isPresented: styleEditorPresentation(store, anchor: nil), arrowEdge: .top) {
|
||||
StyleEditorPopover(store: store, recents: appModel.styleRecents)
|
||||
}
|
||||
// The board is a focus target so the grammar keys reach it at all. The focus *ring* is off:
|
||||
// the strip is the window's content, not a control, and a rectangle around the whole board
|
||||
// would read as an error state.
|
||||
@@ -105,6 +115,27 @@ struct BoardView: View {
|
||||
.onKeyPress(.escape) { handleEscape() }
|
||||
}
|
||||
|
||||
// MARK: - Styling
|
||||
|
||||
/// The board's `background`, painting "the board window's content background (the surface behind
|
||||
/// and between lanes)" (03-board-ui.md § Styling ▸ Capabilities).
|
||||
///
|
||||
/// Unlike the lane band and the card stripe this one is a **fill**, because at board level that
|
||||
/// is what the design asks for — and it is why the board is the level 10-accessibility.md binds
|
||||
/// its ≥ 4.5:1 rule to: text does sit on it. That runtime contrast computation (a hex background's
|
||||
/// text colour, recomputed against the composited backdrop on appearance change) is not this
|
||||
/// card's — what ships here is the palette path, whose twelve pairs are AA-verified at design
|
||||
/// time.
|
||||
///
|
||||
/// A value that resolves to nothing paints nothing, so the window keeps the standard background:
|
||||
/// the same lenient degrade as the other two levels, and the bytes stay as written.
|
||||
@ViewBuilder
|
||||
private var boardBackground: some View {
|
||||
if let color = Palette.color(for: store.snapshot.background) {
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Lanes
|
||||
|
||||
/// One lane's strip slot, plus its trailing grab strip.
|
||||
|
||||
Reference in New Issue
Block a user