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:
@@ -35,9 +35,11 @@ struct BoardGitBranchSurface: Equatable {
|
||||
/// its own sentence instead (`unreadableNote`), and the branch line has no answer to wait for.
|
||||
let isRepositoryUnreadable: Bool
|
||||
|
||||
/// Whether the branch controls accept a click — the popover's switch picker, and the settings
|
||||
/// sheet's create field (`BoardSettingsSheet`), which resolves this same surface so that a
|
||||
/// paused repository, a read-only board and a switch in flight close both by one rule.
|
||||
/// Whether the branch controls accept a click — the switch picker, and the New Branch… reveal
|
||||
/// behind its divider (`BoardGitControls`), so that a paused repository, a read-only board and a
|
||||
/// switch in flight close the whole branch affordance by one rule. (The settings sheet's standing
|
||||
/// Create field resolved this same surface between 2026-07-31 and the 2026-08-07 reversal; the
|
||||
/// field came back into the menu, the rule never moved.)
|
||||
let controlsEnabled: Bool
|
||||
|
||||
/// The line the branch display is read as by VoiceOver.
|
||||
@@ -108,10 +110,16 @@ struct BoardGitBranchSurface: Equatable {
|
||||
/// **The popover's git section on a board that has a repository** (03-board-ui.md ▸ Board popover;
|
||||
/// 06-history-undo.md ▸ Branch switching).
|
||||
///
|
||||
/// **The daily face, and only that** (the 2026-07-31 popover/sheet split): the branch display with
|
||||
/// its **switch** picker, and the pause explanation when the surface is held. Branch *creation* and
|
||||
/// the commit-identity fields left with the split — they are setup, and setup's home is the board
|
||||
/// settings sheet (`BoardSettingsSheet`), which the section's Board Settings… row opens.
|
||||
/// **The whole branch affordance**: the branch display with its **switch** picker, the *New Branch…*
|
||||
/// entry behind that menu's divider and the inline field it reveals, and the pause explanation when
|
||||
/// the surface is held.
|
||||
///
|
||||
/// **Creation left and came back.** The 2026-07-31 popover/sheet split moved it to the board settings
|
||||
/// sheet as a standing Create field; the **2026-08-07 reversal** retired that sheet and restored the
|
||||
/// shape the sheet's own doc comment had described as "the right shape *there*" — an entry inside the
|
||||
/// switch menu that reveals an inline field. The commit-identity fields came back the same day, to
|
||||
/// the Git tab a level up (`BoardGitSetup.swift`), which is why they are not here: they are the
|
||||
/// board's setup, and this is the branch.
|
||||
///
|
||||
/// **Shaped for the half that is not here yet.** Remote tracking, Pull/Push and the status badges are
|
||||
/// 07-sync-collab.md's own cards, and this section is arranged so they join as one more block under
|
||||
@@ -125,6 +133,20 @@ struct BoardGitControls: View {
|
||||
/// refuses a checkout most of all — it rewrites the tree the lock exists to stop describing.
|
||||
let isEnabled: Bool
|
||||
|
||||
/// Whether **New Branch…** has been picked and its field is standing open — the reveal, which is
|
||||
/// the menu entry's whole behaviour (03-board-ui.md ▸ Board popover ▸ Git tab, 2026-08-07). It
|
||||
/// starts closed on every popover open, because the popover is rebuilt fresh each time
|
||||
/// (`BoardInfoWidget`), which is exactly the transience the reveal shape is for.
|
||||
@State private var isCreatingBranch = false
|
||||
|
||||
/// The name being typed, uncommitted. Cleared by a create and by Escape's first press.
|
||||
@State private var draft = ""
|
||||
|
||||
/// Focus lands in the field the moment it appears, unlike the popover's rename field: this one is
|
||||
/// opened by a gesture that means "name a branch now", which is the inline editors' case rather
|
||||
/// than the configuration-surface case (11-command-nexus.md's class **C** distinguishes the two).
|
||||
@FocusState private var isFieldFocused: Bool
|
||||
|
||||
private var surface: BoardGitBranchSurface {
|
||||
BoardGitBranchSurface.resolve(
|
||||
branch: git.branch,
|
||||
@@ -138,6 +160,10 @@ struct BoardGitControls: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
branchRow
|
||||
|
||||
if isCreatingBranch {
|
||||
creationField
|
||||
}
|
||||
|
||||
// **The unreadable repository names itself in its own sentence** (06 ▸ Rules, the
|
||||
// corrupt-`.git` loud failure) — checked before the pause note because it *is* a pause,
|
||||
// and the pause note's second line ("finishing it belongs to the tool that started it")
|
||||
@@ -168,12 +194,17 @@ struct BoardGitControls: View {
|
||||
/// makes "branch/source display and switching" one affordance rather than a label with a button
|
||||
/// 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. **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.
|
||||
/// **Two halves under a divider** since the 2026-08-07 reversal: the switch targets above, and
|
||||
/// **New Branch…** below, which reveals the inline field under this row rather than acting. That
|
||||
/// is the shape the 2026-07-31 split took creation *out* of — into the settings sheet's standing
|
||||
/// Create field — and the shape the reversal restored when the sheet retired; the sheet's own doc
|
||||
/// comment had called it "the right shape *there*", meaning here.
|
||||
///
|
||||
/// **The menu is therefore never empty**, which is what the divider guarantees rather than the
|
||||
/// content: New Branch… always applies to a board that has a repository. **A single-branch board
|
||||
/// still opens onto a disabled explanatory row** in the upper half (03-board-ui.md ▸ Board popover
|
||||
/// ▸ Git tab, ruled 2026-08-06 and built with the tab): an unexplained gap above the divider would
|
||||
/// read as a menu that lost something, not as a board with one branch.
|
||||
private var branchRow: some View {
|
||||
// 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
|
||||
@@ -188,11 +219,12 @@ struct BoardGitControls: View {
|
||||
Menu {
|
||||
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…
|
||||
// ruled 2026-08-06). It teaches the one thing an empty upper half leaves a user
|
||||
// to guess at: *why* there is nothing to pick — this half holds only the
|
||||
// **other** local branches, and there are none. (It carried a second teaching
|
||||
// while creation lived in the settings sheet, standing where "New Branch…" used
|
||||
// to; the 2026-08-07 reversal put that entry back one line below, so the row's
|
||||
// second job is done and the sentence is now exactly what it says.)
|
||||
//
|
||||
// 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
|
||||
@@ -207,6 +239,15 @@ struct BoardGitControls: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
// **Creation, revealed rather than performed** — the entry opens the field below the
|
||||
// row and nothing else, so the act of naming a branch happens in the surface the user
|
||||
// can see and correct rather than inside a menu that has already closed.
|
||||
Button("New Branch…") {
|
||||
isCreatingBranch = true
|
||||
}
|
||||
} label: {
|
||||
Text(surface.branchLabel)
|
||||
.font(.callout)
|
||||
@@ -254,6 +295,71 @@ struct BoardGitControls: View {
|
||||
branches.filter { $0 != current }
|
||||
}
|
||||
|
||||
// MARK: The creation reveal
|
||||
|
||||
/// **What New Branch… reveals** (03-board-ui.md ▸ Board popover ▸ Git tab, restored 2026-08-07):
|
||||
/// a name field and a Create button, under the branch row, for exactly as long as the user is
|
||||
/// naming something. It is a detour off the daily face and it looks like one — which is the
|
||||
/// argument the 2026-07-31 split made *against* it in a sheet, and for it here.
|
||||
///
|
||||
/// **The sequence is not this view's.** `GitBranchSwitcher.createAndSwitch(to:)` runs the
|
||||
/// identical settle → flush → stamp → switch sequence the picker's switch does — "no at-HEAD fast
|
||||
/// path" (06-history-undo.md ▸ Branch switching, blessed 2026-07-31) — and `create()` calls
|
||||
/// exactly that method.
|
||||
///
|
||||
/// **Failures answer where they were asked**: the section's existing switcher caption below is
|
||||
/// this control's answer too (06 ▸ Rules, the form-anchored rule as the 2026-08-06 create-and-
|
||||
/// switch overlap settled it — "inline while the asking surface is up, banner once it is gone").
|
||||
/// The popover *is* the asking surface now that the sheet is retired, so there is one caption for
|
||||
/// both halves of the branch affordance and no second slot to keep in sync.
|
||||
///
|
||||
/// No `ProgressView` of its own, for the same reason: `branchRow` already shows the in-flight
|
||||
/// spinner, and a switch has one state whichever control started it.
|
||||
private var creationField: some View {
|
||||
HStack(spacing: 6) {
|
||||
TextField("New branch name", text: $draft)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.lineLimit(1)
|
||||
.focused($isFieldFocused)
|
||||
.onAppear { isFieldFocused = true }
|
||||
.onSubmit { create() }
|
||||
// **Escape steps outward one layer per press** (04-interactions.md ▸ Grammar, the
|
||||
// rename field's rule and the retired sheet section's before it): a dirty field
|
||||
// abandons its draft and the reveal stays open; an empty one closes the reveal, which
|
||||
// is the layer above it; a further press then reaches the popover's own dismissal.
|
||||
.onKeyPress(.escape) {
|
||||
if !draft.isEmpty {
|
||||
draft = ""
|
||||
return .handled
|
||||
}
|
||||
isCreatingBranch = false
|
||||
return .handled
|
||||
}
|
||||
.accessibilityLabel("New branch name")
|
||||
|
||||
Button("Create", action: create)
|
||||
.disabled(trimmedDraft.isEmpty)
|
||||
}
|
||||
// The branch controls' one rule, applied to the reveal as it is to the picker: a paused
|
||||
// repository, a read-only board and a switch in flight close it (`BoardGitBranchSurface`).
|
||||
.disabled(!surface.controlsEnabled)
|
||||
}
|
||||
|
||||
private var trimmedDraft: String {
|
||||
draft.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
/// Creates the named branch and switches to it, then puts the reveal away — a create is the end
|
||||
/// of the detour, and a field left standing over a branch that now exists would be inviting a
|
||||
/// second one nobody asked for.
|
||||
private func create() {
|
||||
let name = trimmedDraft
|
||||
guard !name.isEmpty, surface.controlsEnabled else { return }
|
||||
draft = ""
|
||||
isCreatingBranch = false
|
||||
Task { await git.switcher?.createAndSwitch(to: name) }
|
||||
}
|
||||
|
||||
// MARK: The pause
|
||||
|
||||
/// **The abnormal-state surface** (06 ▸ Rules ▸ Abnormal repo states) — deferred here from the
|
||||
|
||||
Reference in New Issue
Block a user