The board settings sheet — setup leaves the popover for a home a stray click can't dismiss
The popover/sheet split lands: a board-scoped, titled, sectioned sheet on the board window hosts everything setup-shaped, opened from the popover's Board Settings… row and the new Board ▸ Board Settings… menu row. The three existing setup controls relocate — add-git (whose noteFormVisible lines now make the sheet the form-anchored failure surface), branch creation (a standing field; create-and-switch runs the identical settle sequence), and the commit-identity fields (the 2s visibility-scoped poll rides with them). The popover keeps the daily face and its postures; its Pro/mode-none section becomes header + door (.addGit renamed .noRepository). Availability is derived from the section inventory (Pro + mode none or git), so pro-m2's sections can't drift from the doors; the sheet's fields join the caret-chord disable set. The audit suite pins what the free fixture can reach; the sheet's own audit is manual until a tier override is ruled. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -87,13 +87,17 @@ extension BoardStore {
|
||||
/// > enabled menu key equivalent fires before the field ever sees the key, and ⌘←/⌘→ are the
|
||||
/// > standard line-start/end caret chords.
|
||||
///
|
||||
/// Four text surfaces, answered four ways, and only two of them are here:
|
||||
/// Five text surfaces, answered four ways, and only three of them are here:
|
||||
///
|
||||
/// - **Inline title editors** are `acceptsBoardMutations`', through the focused-editor rule — a
|
||||
/// broader lockdown that already covers these two items.
|
||||
/// - **The board popover's fields** are covered by disabling while the popover is open at all —
|
||||
/// coarser than per-field focus, but it is a configuration surface (04's carve-out) and no lane
|
||||
/// move belongs under it.
|
||||
/// - **The board settings sheet's fields** are the popover's rule for the popover's reason, and the
|
||||
/// surface the 2026-07-31 split moved most of those fields *to* (branch name, commit identity, and
|
||||
/// pro-m2's remote URL and credentials): the sheet is the other half of 04's configuration
|
||||
/// carve-out, and a lane move under a modal settings surface is not a gesture that exists.
|
||||
/// - **The search field** is per-focus and exact (`BoardSearchPresentation.isFocused`), which it has
|
||||
/// to be: the field's own rule is that board commands *stay enabled* while it holds the keyboard
|
||||
/// (04 § Search), so these two are the narrow exception to it and nothing coarser would do.
|
||||
@@ -103,8 +107,14 @@ extension BoardStore {
|
||||
/// Stated once because the two command groups must not drift: a rule with two implementations is a
|
||||
/// rule with two chances to forget a surface.
|
||||
@MainActor
|
||||
func caretChordsYield(boardInfo: BoardInfoPresentation?, search: BoardSearchPresentation?) -> Bool {
|
||||
boardInfo?.isPresented == true || search?.isFocused == true
|
||||
func caretChordsYield(
|
||||
boardInfo: BoardInfoPresentation?,
|
||||
boardSettings: BoardSettingsPresentation?,
|
||||
search: BoardSearchPresentation?
|
||||
) -> Bool {
|
||||
boardInfo?.isPresented == true
|
||||
|| boardSettings?.isPresented == true
|
||||
|| search?.isFocused == true
|
||||
}
|
||||
|
||||
// MARK: - Open Card
|
||||
@@ -276,11 +286,13 @@ struct MoveCardCommands: View {
|
||||
/// **Caret chords yield to any focused text control** (04-interactions.md ▸ Grammar, settled):
|
||||
/// ⌘←/⌘→ are the standard line-start/end chords, and an enabled key equivalent fires before a
|
||||
/// field ever sees the key. Which surfaces that covers, and how each is answered, is
|
||||
/// `caretChordsYield(boardInfo:search:)`'s doc comment — shared verbatim with the width pair below.
|
||||
/// `caretChordsYield(boardInfo:boardSettings:search:)`'s doc comment — shared verbatim with the
|
||||
/// width pair below.
|
||||
struct MoveLaneCommands: View {
|
||||
|
||||
@FocusedValue(\.boardStore) private var store
|
||||
@FocusedValue(\.boardInfo) private var boardInfo
|
||||
@FocusedValue(\.boardSettings) private var boardSettings
|
||||
@FocusedValue(\.boardSearch) private var search
|
||||
|
||||
var body: some View {
|
||||
@@ -298,7 +310,7 @@ struct MoveLaneCommands: View {
|
||||
}
|
||||
|
||||
private var yieldsCaretChords: Bool {
|
||||
caretChordsYield(boardInfo: boardInfo, search: search)
|
||||
caretChordsYield(boardInfo: boardInfo, boardSettings: boardSettings, search: search)
|
||||
}
|
||||
|
||||
/// The sole selected live lane and the display slot one step would put it in — `nil` when there
|
||||
@@ -415,6 +427,48 @@ struct BoardInfoCommand: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Board Settings
|
||||
|
||||
/// Board ▸ Board Settings… — no default chord (11-command-nexus.md: "— (no default)"), and the board
|
||||
/// settings sheet's second door, the popover's row being the first (03-board-ui.md ▸ Board settings
|
||||
/// sheet).
|
||||
///
|
||||
/// **"Remappable" asks nothing of this file.** The remapping mechanism is macOS's own — System
|
||||
/// Settings ▸ Keyboard ▸ App Shortcuts, keyed on the menu item's *title* (04-interactions.md ▸
|
||||
/// Configurable bindings) — so all a chordless row owes it is a stable title, which the Nexus fixes.
|
||||
/// A `Button` with no `keyboardShortcut` is therefore the whole implementation, exactly as File ▸
|
||||
/// Save as Template and Board ▸ Rename are.
|
||||
///
|
||||
/// ### It opens; it never toggles
|
||||
///
|
||||
/// Board Info ⌘I toggles because a popover reached by a chord would otherwise have no keyboard way
|
||||
/// out. A sheet has one built in (Done, and Escape through it), and the menu is behind the sheet
|
||||
/// while it is up — so a toggling row would be a second exit nobody can reach.
|
||||
///
|
||||
/// ### Validation: scope, then reachability — and never the lock
|
||||
///
|
||||
/// The row stays **visible and disabled** where the sheet cannot exist (`BoardSettingsAvailability`,
|
||||
/// which carries the reasoning): the free tier, and a Pro board nested inside someone else's
|
||||
/// repository. That is standard menu validation, and it is the deliberate asymmetry with the popover
|
||||
/// row, which is *absent* there instead — a menu is an inventory of the app, a popover section is a
|
||||
/// description of this board.
|
||||
///
|
||||
/// The read-only lock does not close it, for Board Info's reason: a settings sheet is *configuration*
|
||||
/// (04-interactions.md ▸ The map's carve-out), a locked board is exactly when a user may want to read
|
||||
/// its git setup, and the controls inside disable themselves in place (03 ▸ Board settings sheet).
|
||||
struct BoardSettingsCommand: View {
|
||||
|
||||
@FocusedValue(\.boardStore) private var store
|
||||
@FocusedValue(\.boardSettings) private var presentation
|
||||
|
||||
var body: some View {
|
||||
Button("Board Settings…") {
|
||||
presentation?.present()
|
||||
}
|
||||
.disabled(store == nil || presentation?.isReachable != true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Rename
|
||||
|
||||
/// Board ▸ Rename — no default chord, deliberately (11-command-nexus.md: "— (cards: Return in
|
||||
@@ -491,6 +545,7 @@ struct LaneWidthCommands: View {
|
||||
|
||||
@FocusedValue(\.boardStore) private var store
|
||||
@FocusedValue(\.boardInfo) private var boardInfo
|
||||
@FocusedValue(\.boardSettings) private var boardSettings
|
||||
@FocusedValue(\.boardSearch) private var search
|
||||
|
||||
var body: some View {
|
||||
@@ -508,7 +563,7 @@ struct LaneWidthCommands: View {
|
||||
}
|
||||
|
||||
private var yieldsCaretChords: Bool {
|
||||
caretChordsYield(boardInfo: boardInfo, search: search)
|
||||
caretChordsYield(boardInfo: boardInfo, boardSettings: boardSettings, search: search)
|
||||
}
|
||||
|
||||
/// The selected live lanes, in snapshot order — the batch, and the items' validation.
|
||||
|
||||
Reference in New Issue
Block a user