The popover/sheet split reverses — settings fold into the Git tab, the widget stacks name over branch
The titlebar widget becomes a two-line identity block: the board glyph at 22pt spanning both lines, the title over the branch (git-mode only, smaller and secondary), the em-dash retired. New Branch… returns to the switch menu behind a divider, revealing an inline name field — the pre-split shape. The board settings sheet retires whole: add-git and commit identity render inline in the Git tab's postures (BoardGitSetup.swift), the availability rule collapses into BoardGitSetupSection.resolve, and Board ▸ Board Settings… leaves the menu bar. Where 07's remote/credential setup surfaces land is deliberately left open — filed on the Redesign board. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -1,16 +1,21 @@
|
||||
import Testing
|
||||
@testable import Kanban
|
||||
|
||||
/// `caretChordsYield(boardInfo:boardSettings:search:)` — 04-interactions.md ▸ Grammar's caret-chords
|
||||
/// `caretChordsYield(boardInfo:search:)` — 04-interactions.md ▸ Grammar's caret-chords
|
||||
/// rule as one expression, and `BoardCommands.swift`'s single seam for it: Board ▸ Move Left/Move
|
||||
/// Right ⌘←/⌘→ and the lane-width pair ⌥⌘←/⌥⌘→ disable via menu validation whenever *any* text
|
||||
/// control has keyboard focus, because ⌘←/⌘→ are the standard line-start/end caret chords and an
|
||||
/// enabled key equivalent fires before a field ever sees the key.
|
||||
///
|
||||
/// The function reads exactly three flags and nothing else, so every test here constructs
|
||||
/// `BoardInfoPresentation`, `BoardSettingsPresentation` and `BoardSearchPresentation` directly rather
|
||||
/// than through a `BoardStore` — a fixture that stood up a board would be exercising machinery this
|
||||
/// seam never touches.
|
||||
/// The function reads exactly two flags and nothing else, so every test here constructs
|
||||
/// `BoardInfoPresentation` and `BoardSearchPresentation` directly rather than through a `BoardStore`
|
||||
/// — a fixture that stood up a board would be exercising machinery this seam never touches.
|
||||
///
|
||||
/// **It read three until 2026-08-07.** The 2026-07-31 popover/sheet split gave the board settings
|
||||
/// sheet its own flag here, because the branch-name and commit-identity fields had moved onto it; the
|
||||
/// reversal retired that sheet and brought those fields back into the popover's Git tab, so the
|
||||
/// popover's own open-at-all clause covers every configuration field again and the third disjunct
|
||||
/// went with the surface it described.
|
||||
@MainActor
|
||||
@Suite("caretChordsYield ▸ the caret-chords rule")
|
||||
struct CaretChordTests {
|
||||
@@ -22,7 +27,7 @@ struct CaretChordTests {
|
||||
// "Card-window fields need nothing: those windows never publish a boardStore, so both items
|
||||
// are already scopeless there" (caretChordsYield's doc comment) — nil/nil is that window's
|
||||
// steady state, not a corner case.
|
||||
#expect(caretChordsYield(boardInfo: nil, boardSettings: nil, search: nil) == false)
|
||||
#expect(caretChordsYield(boardInfo: nil, search: nil) == false)
|
||||
}
|
||||
|
||||
// MARK: The board popover, alone
|
||||
@@ -30,46 +35,13 @@ struct CaretChordTests {
|
||||
@Test("The board popover open yields; closed, it does not")
|
||||
func popoverPresence() {
|
||||
let boardInfo = BoardInfoPresentation()
|
||||
#expect(caretChordsYield(boardInfo: boardInfo, boardSettings: nil, search: nil) == false, "closed by default")
|
||||
#expect(caretChordsYield(boardInfo: boardInfo, search: nil) == false, "closed by default")
|
||||
|
||||
boardInfo.isPresented = true
|
||||
#expect(caretChordsYield(boardInfo: boardInfo, boardSettings: nil, search: nil) == true)
|
||||
#expect(caretChordsYield(boardInfo: boardInfo, search: nil) == true)
|
||||
|
||||
boardInfo.isPresented = false
|
||||
#expect(caretChordsYield(boardInfo: boardInfo, boardSettings: nil, search: nil) == false, "closing re-enables the chords")
|
||||
}
|
||||
|
||||
// MARK: The settings sheet, alone
|
||||
|
||||
@Test("The settings sheet open yields; closed, it does not")
|
||||
func settingsSheetPresence() {
|
||||
// The sheet joined this seam with the 2026-07-31 popover/sheet split: it is where the branch
|
||||
// name field and the commit-identity fields live now, so the surface that used to be covered
|
||||
// by "the popover is open" has to be covered by "the sheet is up" as well.
|
||||
let settings = BoardSettingsPresentation()
|
||||
#expect(caretChordsYield(boardInfo: nil, boardSettings: settings, search: nil) == false, "closed by default")
|
||||
|
||||
settings.isPresented = true
|
||||
#expect(caretChordsYield(boardInfo: nil, boardSettings: settings, search: nil) == true)
|
||||
|
||||
settings.isPresented = false
|
||||
#expect(
|
||||
caretChordsYield(boardInfo: nil, boardSettings: settings, search: nil) == false,
|
||||
"dismissing re-enables the chords"
|
||||
)
|
||||
}
|
||||
|
||||
@Test("The sheet's own reachability is not this seam's question — a presented sheet yields either way")
|
||||
func presentationRatherThanReachability() {
|
||||
// `isReachable` gates the two *doors* (`BoardSettingsAvailability`); this function reads the
|
||||
// flag that says a sheet is on screen. A presentation left unadopted reads free-tier and
|
||||
// therefore unreachable, and if something ever put its flag up anyway the chords must still
|
||||
// yield — what a caret chord competes with is a field that exists, not a tier.
|
||||
let settings = BoardSettingsPresentation()
|
||||
#expect(settings.isReachable == false, "unadopted reads as the free tier")
|
||||
|
||||
settings.isPresented = true
|
||||
#expect(caretChordsYield(boardInfo: nil, boardSettings: settings, search: nil) == true)
|
||||
#expect(caretChordsYield(boardInfo: boardInfo, search: nil) == false, "closing re-enables the chords")
|
||||
}
|
||||
|
||||
// MARK: The search field, alone
|
||||
@@ -77,13 +49,13 @@ struct CaretChordTests {
|
||||
@Test("The search field focused yields; unfocused, it does not")
|
||||
func searchFocus() {
|
||||
let search = BoardSearchPresentation()
|
||||
#expect(caretChordsYield(boardInfo: nil, boardSettings: nil, search: search) == false, "unfocused by default")
|
||||
#expect(caretChordsYield(boardInfo: nil, search: search) == false, "unfocused by default")
|
||||
|
||||
search.isFocused = true
|
||||
#expect(caretChordsYield(boardInfo: nil, boardSettings: nil, search: search) == true)
|
||||
#expect(caretChordsYield(boardInfo: nil, search: search) == true)
|
||||
|
||||
search.isFocused = false
|
||||
#expect(caretChordsYield(boardInfo: nil, boardSettings: nil, search: search) == false, "losing focus re-enables the chords")
|
||||
#expect(caretChordsYield(boardInfo: nil, search: search) == false, "losing focus re-enables the chords")
|
||||
}
|
||||
|
||||
// MARK: Both surfaces together
|
||||
@@ -98,32 +70,22 @@ struct CaretChordTests {
|
||||
focusedSearch.isFocused = true
|
||||
|
||||
// One side published and inert, the other absent (the still-loading-window shape): false.
|
||||
#expect(caretChordsYield(boardInfo: closedInfo, boardSettings: nil, search: nil) == false)
|
||||
#expect(caretChordsYield(boardInfo: nil, boardSettings: nil, search: unfocusedSearch) == false)
|
||||
#expect(caretChordsYield(boardInfo: closedInfo, search: nil) == false)
|
||||
#expect(caretChordsYield(boardInfo: nil, search: unfocusedSearch) == false)
|
||||
|
||||
// One side published and active, the other absent: true.
|
||||
#expect(caretChordsYield(boardInfo: openInfo, boardSettings: nil, search: nil) == true)
|
||||
#expect(caretChordsYield(boardInfo: nil, boardSettings: nil, search: focusedSearch) == true)
|
||||
#expect(caretChordsYield(boardInfo: openInfo, search: nil) == true)
|
||||
#expect(caretChordsYield(boardInfo: nil, search: focusedSearch) == true)
|
||||
|
||||
// Both published, both inert: false — the popover being open at all and the field holding
|
||||
// focus are each read independently, so neither's mere presence counts on its own.
|
||||
#expect(caretChordsYield(boardInfo: closedInfo, boardSettings: nil, search: unfocusedSearch) == false)
|
||||
#expect(caretChordsYield(boardInfo: closedInfo, search: unfocusedSearch) == false)
|
||||
|
||||
// Both published, one or both active: true. This is an `||`, not an `&&` — one live text
|
||||
// surface is enough to send the chords to it, whatever the other surface is doing.
|
||||
#expect(caretChordsYield(boardInfo: openInfo, boardSettings: nil, search: unfocusedSearch) == true)
|
||||
#expect(caretChordsYield(boardInfo: closedInfo, boardSettings: nil, search: focusedSearch) == true)
|
||||
#expect(caretChordsYield(boardInfo: openInfo, boardSettings: nil, search: focusedSearch) == true)
|
||||
|
||||
// And the third surface composes the same way — inert beside two inert siblings, sufficient
|
||||
// beside them when it is up.
|
||||
let closedSheet = BoardSettingsPresentation()
|
||||
let openSheet = BoardSettingsPresentation()
|
||||
openSheet.isPresented = true
|
||||
|
||||
#expect(caretChordsYield(boardInfo: closedInfo, boardSettings: closedSheet, search: unfocusedSearch) == false)
|
||||
#expect(caretChordsYield(boardInfo: closedInfo, boardSettings: openSheet, search: unfocusedSearch) == true)
|
||||
#expect(caretChordsYield(boardInfo: openInfo, boardSettings: openSheet, search: focusedSearch) == true)
|
||||
#expect(caretChordsYield(boardInfo: openInfo, search: unfocusedSearch) == true)
|
||||
#expect(caretChordsYield(boardInfo: closedInfo, search: focusedSearch) == true)
|
||||
#expect(caretChordsYield(boardInfo: openInfo, search: focusedSearch) == true)
|
||||
}
|
||||
|
||||
// MARK: Inline editors are a different seam
|
||||
@@ -142,6 +104,6 @@ struct CaretChordTests {
|
||||
|
||||
// No board popover, no search focus — caretChordsYield answers false regardless of the open
|
||||
// editor above, because it never reads isEditingInline at all.
|
||||
#expect(caretChordsYield(boardInfo: nil, boardSettings: nil, search: nil) == false)
|
||||
#expect(caretChordsYield(boardInfo: nil, search: nil) == false)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user