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:
@@ -121,6 +121,48 @@ struct BoardRenameCommand: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Style
|
||||
|
||||
/// Board ▸ Style… (⌥⌘S) — the style editor's menu-bar anchor (11-command-nexus.md;
|
||||
/// 03-board-ui.md § Styling ▸ Controls).
|
||||
///
|
||||
/// **Selection-aware, with the board as the empty-selection case**: "Board window: selected cards or
|
||||
/// lane; nothing selected = the board". The item does not present anything itself — it opens the
|
||||
/// session (`TransientBoardState.beginStyleEditor`) and the board window's anchors decide which
|
||||
/// surface hosts the popover, which is what keeps the presentation attached to what is being styled
|
||||
/// rather than to the menu bar.
|
||||
///
|
||||
/// Validation is `acceptsBoardMutations` — the lock and the focused-editor rule, the latter naming
|
||||
/// Style in its own list of board-scoped commands (04-interactions.md ▸ Grammar) — plus one rule of
|
||||
/// its own: **a tombstoned selection disables it rather than falling through to the board.**
|
||||
/// Everything edit-shaped is disabled on tombstoned selections (04 ▸ The trash), and quietly
|
||||
/// restyling the board because the user had a trashed card selected would be the silent retarget
|
||||
/// 03 forbids.
|
||||
struct BoardStyleCommand: View {
|
||||
|
||||
@FocusedValue(\.boardStore) private var store
|
||||
|
||||
var body: some View {
|
||||
Button("Style…") {
|
||||
guard let store, let target = styleTarget else { return }
|
||||
store.transient.beginStyleEditor(for: target)
|
||||
}
|
||||
.keyboardShortcut("s", modifiers: [.option, .command])
|
||||
.disabled(styleTarget == nil)
|
||||
}
|
||||
|
||||
private var styleTarget: StyleTarget? {
|
||||
guard let store, store.acceptsBoardMutations else { return nil }
|
||||
let selection = store.selection
|
||||
guard !selection.isEmpty else { return .board }
|
||||
guard selection.liveness == .live else { return nil }
|
||||
// Re-resolved against the snapshot on the way in, so the session starts out holding only
|
||||
// items that render — the same universe its own reload rule will hold it to.
|
||||
let live = selection.resolved(against: store.snapshot).ids
|
||||
return live.isEmpty ? nil : .items(live)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Lane width items
|
||||
|
||||
/// Increase / Decrease Lane Width — **the width stepper's keyboard face** (03-board-ui.md § Lane,
|
||||
|
||||
Reference in New Issue
Block a user