import SwiftUI // MARK: - The focused board /// The frontmost board window's store, published into the focus system by `BoardWindowHost` so menu /// items can act on "the board in front" without the app model keeping a which-window-is-key /// register of its own. /// /// `focusedSceneValue` rather than `focusedValue`: the value is the *window's*, not any particular /// control's, so it stays available whatever inside the board has keyboard focus — which is what a /// menu item validating against the selection needs. struct FocusedBoardStoreKey: FocusedValueKey { typealias Value = BoardStore } extension FocusedValues { var boardStore: BoardStore? { get { self[FocusedBoardStoreKey.self] } set { self[FocusedBoardStoreKey.self] = newValue } } } // MARK: - Lane width items /// Increase / Decrease Lane Width — **the width stepper's keyboard face** (03-board-ui.md § Lane, /// 11-command-nexus.md), and therefore the *re-divide* mechanism: each step re-divides the existing /// window width across the new unit total, compressing the siblings. They never touch the window's /// size — window-growing behaviour belongs to the right-edge drag alone — and they are uncapped, so /// widths beyond what the screen can fit stay reachable here even though the drag hard-stops. /// /// **Validation is the sole-selected-lane rule.** Both items are enabled only when the focused /// board's selection resolves to exactly one live lane; a card selection, a multi-selection, a /// trash-side selection and an empty one all disable them. Decrease additionally disables at one /// unit, which is the floor. Nothing selects a lane yet — the lane-chrome card wires the header /// click (04-interactions.md ▸ Selection) — so these validate-disable in today's build, which is /// expected rather than broken. struct LaneWidthCommands: View { @FocusedValue(\.boardStore) private var store var body: some View { Button("Increase Lane Width") { step(by: 1) } .keyboardShortcut(.rightArrow, modifiers: [.option, .command]) .disabled(selectedLane == nil) Button("Decrease Lane Width") { step(by: -1) } .keyboardShortcut(.leftArrow, modifiers: [.option, .command]) .disabled(!canDecrease) } /// The sole selected live lane, or `nil` — the whole of these items' validation. /// /// A read-only board disables every mutating command (02-architecture.md § The lock's scope), so /// the lock is folded in here rather than left for the write to refuse: an item that is going to /// fail should not look available. private var selectedLane: Lane? { guard let store, !store.isReadOnly else { return nil } let selection = store.selection guard selection.liveness == .live, selection.ids.count == 1, let id = selection.ids.first else { return nil } return store.snapshot.lanes.first { $0.id == id && !$0.isDeleted } } /// A one-unit lane cannot shrink: `width` is ≥ 1 (03-board-ui.md § Lane), and an item whose only /// outcome is a no-op reads better disabled than dead. private var canDecrease: Bool { guard let lane = selectedLane else { return false } return LaneLayoutMath.displayUnits(of: lane) > 1 } private func step(by delta: Int) { guard let store, let lane = selectedLane else { return } store.setLaneWidth(lane.id, units: LaneLayoutMath.displayUnits(of: lane) + delta) } }