Give the single-branch switch picker its disabled explanatory row

With branch creation relocated to the settings sheet, a single-branch
board's switch menu opened onto nothing and read as broken (ruled
2026-08-06, built with the Git tab): a bare Text — AppKit's standard
disabled item, read by VoiceOver as disabled text — now says "No other
branches" where the switch entries would be. The filter behind it becomes
the pure switchTargets(branches:current:) seam, pinned by
BranchSwitchTargetTests: current excluded, nil current passes all through,
repository order preserved.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-08-07 19:12:01 -04:00
parent 7d7e892617
commit 93a3423e6b
2 changed files with 106 additions and 13 deletions
+50 -1
View File
@@ -661,7 +661,7 @@ struct BranchSwitchSequenceTests {
var asked = 0
switcher.settleSessions = { asked += 1; return .proceed }
// The picker never offers it (`BoardGitControls.otherBranches`), and the operation is a no-op
// The picker never offers it (`BoardGitControls.switchTargets`), and the operation is a no-op
// if one ever arrives: same branch, same tree, nothing to announce.
#expect(await switcher.switchTo("main"))
#expect(GitRepository.branchName(at: fixture.root) == "main")
@@ -1038,6 +1038,55 @@ struct BoardGitBranchSurfaceTests {
}
}
// MARK: - The switch picker's targets
/// **What the branch menu offers, and what it says when that is nothing** (03-board-ui.md Board
/// popover Git tab).
///
/// `BoardGitControls.switchTargets` is the filter as a pure function, pinned here for the reason
/// `BoardGitBranchSurface.resolve` is pinned above: the rule is the part worth asserting, and the
/// `Menu` it fills is SwiftUI. Its emptiness is load-bearing since 2026-08-06 with branch
/// *creation* relocated to the settings sheet, an empty result is what puts the disabled "No other
/// branches" row on screen in place of a menu that would otherwise open onto nothing and read as
/// broken.
@Suite("Board popover ▸ the switch picker's targets")
struct BranchSwitchTargetTests {
@Test("The branch already checked out is never offered — switching to it would be a no-op")
func theCurrentBranchIsExcluded() {
#expect(
BoardGitControls.switchTargets(branches: ["main", "redesign", "spike"], current: "main")
== ["redesign", "spike"]
)
}
@Test("A single-branch board has no targets at all — which is what the disabled row is for")
func aSingleBranchBoardHasNoTargets() {
#expect(BoardGitControls.switchTargets(branches: ["main"], current: "main").isEmpty)
// The shape a repository with an unborn HEAD is in: no branches listed at all.
#expect(BoardGitControls.switchTargets(branches: [], current: "main").isEmpty)
}
@Test("Before the first branch read every branch is a candidate, rather than none")
func anUnreadCurrentPassesEverythingThrough() {
// `HistoryStore.branch` is `nil` until `refreshBranch()` answers, which is a moment the
// popover is on screen for. Filtering everything out there would be a menu that starts empty
// and fills in the switch's own gate is what decides whether a target can be taken.
#expect(
BoardGitControls.switchTargets(branches: ["main", "redesign"], current: nil)
== ["main", "redesign"]
)
}
@Test("The repository's own order survives — the picker never re-sorts what libgit2 listed")
func orderIsPreserved() {
#expect(
BoardGitControls.switchTargets(branches: ["zeta", "alpha", "main", "beta"], current: "main")
== ["zeta", "alpha", "beta"]
)
}
}
// MARK: - Commit identity
@Suite("Board popover ▸ the commit-identity fields")