Build branch switching and the popover git surface
GitBranchSwitcher holds 06's sequence as one object: settle editors
explicitly (SessionSettleGate — Save All applies raw buffers with
validation and a refused buffer cancels the whole switch; Discard
reverts buffers AND reconciles the session folders against HEAD;
never silent), flush the pending auto-commit, stamp intent in the
per-board registry, bracketed safe checkout (git_checkout_tree
GIT_CHECKOUT_SAFE + set_head — no path passes FORCE, abort
included), one reload via the async wholesale bracket (failed final
reload engages the existing read-only lock), reseed undo/redo from
the new HEAD with redo empty, clear the stamp. Create-and-switch
keeps the full sequence — the tree-cannot-change proof fails under
concurrent writers. Lock contention shows the 02 in-progress row's
waiting state ("waiting for another writer's git lock"), bounded at
30s then failing cleanly naming the lock path.
GitOperationStamp + GitOperationRecovery: the own-leftovers rule as
a pure conjunction — pause state AND matching stamp = the app's own
interrupted operation, aborted to the pre-operation state with a
banner, stamp cleared on success only; either alone defers to the
pause-and-defer stance. Checked where the committer starts.
BoardGitControls replaces the read-only branch line: branch picker,
inline create-and-switch, the abnormal-state pause note in 06's own
words with controls dimmed, and commit-identity fields that read and
write repo-local .git/config (derived default as placeholder, never
value; unfocused resync, focused keystrokes kept; 2s poll while
visible — .git is watcher-filtered by design).
Also fixes a shipped bug from the undo card: plan(reconciling:)
matched card ids as path prefixes, so the reconcile branch was inert
on every board (<lane>/<card> never matches a bare id) — a session
file the restore diff couldn't name (attachment, comment, draft)
survived Discard and landed in the next flush's commit. One shared
component-exact folder-name resolver now serves both Discard paths;
noteDiscarded takes cardFolderName; regression test verified failing
against the pre-fix code.
41 branch tests + the regression; 2374 tests / 409 suites green;
InertGitTests untouched.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -250,7 +250,7 @@ struct BoardInfoView: View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
sectionHeader("Git")
|
||||
if let git {
|
||||
BoardGitBranchLine(git: git)
|
||||
BoardGitControls(git: git, isEnabled: store.acceptsBoardMutations)
|
||||
}
|
||||
}
|
||||
.padding(inset)
|
||||
@@ -368,8 +368,10 @@ enum BoardGitSection: Equatable, CaseIterable {
|
||||
/// Pro, repo-nested: the honest explanation, no action.
|
||||
case repoNested
|
||||
|
||||
/// Pro, git mode: the read-only branch/source line. Branch switching and creation, the commit
|
||||
/// identity fields and the remote controls are later cards — nothing here is a control.
|
||||
/// Pro, git mode: the branch/source line with switching and creation, the abnormal-state
|
||||
/// explanation when the surface is held, and the commit-identity fields (`BoardGitControls`).
|
||||
/// The remote half — tracking, Pull/Push, push-on-commit, authentication — is 07-sync-collab.md's
|
||||
/// own card and joins this same posture.
|
||||
case branch
|
||||
|
||||
static func resolve(tier: Tier, mode: BoardGitMode, hasGitDirectory: Bool) -> BoardGitSection {
|
||||
@@ -437,31 +439,6 @@ private struct BoardGitNestedNote: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// **The branch/source display** (03-board-ui.md ▸ Board popover) — read-only, and deliberately the
|
||||
/// whole of the git-mode section for now: switching, creation, identity and remotes are each their
|
||||
/// own card, and a control shown before it works is worse than one that isn't there yet.
|
||||
///
|
||||
/// The name is read when the popover appears rather than at session composition, so the open path
|
||||
/// never waits on libgit2 (`HistoryStore.branch`).
|
||||
private struct BoardGitBranchLine: View {
|
||||
|
||||
let git: HistoryStore
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "arrow.triangle.branch")
|
||||
.imageScale(.small)
|
||||
.foregroundStyle(.secondary)
|
||||
Text(git.branch ?? "…")
|
||||
.font(.callout)
|
||||
.foregroundStyle(git.branch == nil ? .secondary : .primary)
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityLabel(git.branch.map { "Branch \($0)" } ?? "Reading branch")
|
||||
.task { await git.refreshBranch() }
|
||||
}
|
||||
}
|
||||
|
||||
/// The contextual git note — **a quiet signpost, not a feature** (12-editions.md ▸ The free tier and
|
||||
/// `.git`, settled 2026-07-27, carried through the one-app collapse). The free tier has no git
|
||||
/// integration (that is the Pro subscription's), so this is not a grow-in-place slot the way the old
|
||||
|
||||
Reference in New Issue
Block a user