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:
@@ -67,7 +67,7 @@ struct BoardGitBranchSurface: Equatable {
|
||||
/// loud failure, ruled 2026-07-31: "with the whole git surface paused … and the popover's git
|
||||
/// section naming the state").
|
||||
///
|
||||
/// A sibling of the nested and unverifiable notes (`BoardInfoPopover`) and written in their
|
||||
/// A sibling of the nested and unverifiable notes (`BoardGitTabView`) and written in their
|
||||
/// register — one sentence, the state first and the consequence after — rather than the pause
|
||||
/// note's two lines, because both of *those* lines would be wrong here: nothing is "in
|
||||
/// progress", and there is no tool whose job it is to finish it. What it keeps from the pause
|
||||
@@ -169,20 +169,42 @@ struct BoardGitControls: View {
|
||||
/// beside it.
|
||||
///
|
||||
/// **Switch entries and nothing else** since the 2026-07-31 split — the "New Branch…" entry that
|
||||
/// used to close the menu is the settings sheet's standing Create field now. On a board with one
|
||||
/// branch the menu is therefore *empty*, and that is left honest rather than papered over: the
|
||||
/// label still reads the current branch and still disables with the rest of the git surface, so
|
||||
/// the line goes on saying the one thing it is here to say.
|
||||
/// used to close the menu is the settings sheet's standing Create field now. **A single-branch
|
||||
/// board therefore opens onto a disabled explanatory row** (03-board-ui.md ▸ Board popover ▸ Git
|
||||
/// tab, ruled 2026-08-06 and built with the tab): the earlier posture left the menu genuinely
|
||||
/// empty and called that honest, but honest is not the same as legible — a menu that opens onto
|
||||
/// nothing reads as broken, not as complete.
|
||||
private var branchRow: some View {
|
||||
HStack(spacing: 6) {
|
||||
// Resolved once and handed to both halves of the menu builder: the emptiness *is* the
|
||||
// condition being rendered, so asking twice would be asking the same question of a store
|
||||
// that could answer differently between the two reads.
|
||||
let targets = otherBranches
|
||||
|
||||
return HStack(spacing: 6) {
|
||||
Image(systemName: "arrow.triangle.branch")
|
||||
.imageScale(.small)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
Menu {
|
||||
ForEach(otherBranches, id: \.self) { name in
|
||||
Button(name) {
|
||||
Task { await git.switcher?.switchTo(name) }
|
||||
if targets.isEmpty {
|
||||
// **The single-branch board's row** (03-board-ui.md ▸ Board popover ▸ Git tab,
|
||||
// ruled 2026-08-06). It teaches the two things an empty menu leaves a user to
|
||||
// guess at: *why* there is nothing to pick — this menu holds only the **other**
|
||||
// local branches, and there are none — and, by standing where "New Branch…" used
|
||||
// to, that creation is no longer here at all; it is the settings sheet's Create
|
||||
// field, one row down through Board Settings…
|
||||
//
|
||||
// A bare `Text` inside a `Menu` is AppKit's standard disabled item: greyed,
|
||||
// unclickable, and read by VoiceOver as disabled text rather than as an
|
||||
// actionable row — which is exactly the register a sentence explaining an absence
|
||||
// wants (06 ▸ Rules: teach, never look broken; never a disabled button pretending
|
||||
// to be a control).
|
||||
Text("No other branches")
|
||||
} else {
|
||||
ForEach(targets, id: \.self) { name in
|
||||
Button(name) {
|
||||
Task { await git.switcher?.switchTo(name) }
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
@@ -204,10 +226,32 @@ struct BoardGitControls: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Every local branch except the one already checked out — a picker offering the current branch
|
||||
/// would be offering a no-op, and the switch refuses one anyway.
|
||||
/// This board's switch targets, off the live switcher — see `switchTargets(branches:current:)`
|
||||
/// for the rule itself.
|
||||
private var otherBranches: [String] {
|
||||
(git.switcher?.branches ?? []).filter { $0 != git.branch }
|
||||
Self.switchTargets(branches: git.switcher?.branches ?? [], current: git.branch)
|
||||
}
|
||||
|
||||
/// **Every local branch except the one already checked out** — a picker offering the current
|
||||
/// branch would be offering a no-op, and the switch refuses one anyway
|
||||
/// (`BranchSwitchSequenceTests`' "a switch to the branch already checked out does nothing").
|
||||
///
|
||||
/// A pure static seam rather than a computed property alone, for `BoardGitBranchSurface.resolve`'s
|
||||
/// reason one level up: the *rule* is the part worth pinning (`BranchSwitchTargetTests`) and the
|
||||
/// `Menu` it fills is SwiftUI. It is also the predicate the disabled "No other branches" row hangs
|
||||
/// on (03-board-ui.md ▸ Board popover ▸ Git tab, 2026-08-06), which makes "when is that row
|
||||
/// shown" a question with one testable answer rather than a shape buried in a view builder.
|
||||
///
|
||||
/// A `nil` `current` — the moment before the first branch read answers — passes every branch
|
||||
/// through rather than none: the list is a set of candidates, and the switch's own gate is what
|
||||
/// decides whether one can be taken. `nonisolated` because nothing here touches the view: a pure
|
||||
/// filter over plain values, reachable from a test with no actor to hop to.
|
||||
///
|
||||
/// The repository's own ordering survives untouched — `GitBranchOperation.localBranches` is what
|
||||
/// libgit2 listed, and a picker that re-sorted it would be inventing an order the repository
|
||||
/// never had.
|
||||
nonisolated static func switchTargets(branches: [String], current: String?) -> [String] {
|
||||
branches.filter { $0 != current }
|
||||
}
|
||||
|
||||
// MARK: The pause
|
||||
|
||||
Reference in New Issue
Block a user