The board settings sheet — setup leaves the popover for a home a stray click can't dismiss

The popover/sheet split lands: a board-scoped, titled, sectioned sheet on
the board window hosts everything setup-shaped, opened from the popover's
Board Settings… row and the new Board ▸ Board Settings… menu row. The three
existing setup controls relocate — add-git (whose noteFormVisible lines now
make the sheet the form-anchored failure surface), branch creation (a
standing field; create-and-switch runs the identical settle sequence), and
the commit-identity fields (the 2s visibility-scoped poll rides with them).
The popover keeps the daily face and its postures; its Pro/mode-none
section becomes header + door (.addGit renamed .noRepository). Availability
is derived from the section inventory (Pro + mode none or git), so pro-m2's
sections can't drift from the doors; the sheet's fields join the
caret-chord disable set. The audit suite pins what the free fixture can
reach; the sheet's own audit is manual until a tier override is ruled.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-08-01 12:07:12 -04:00
parent 988a7245a3
commit 8345378972
12 changed files with 1168 additions and 321 deletions
+27 -21
View File
@@ -70,9 +70,10 @@ public final class HistoryStore {
/// dismissing the form clears it ("dismissing the sheet dismisses the stale error"). The other
/// half is `reportFailure`, which posts the banner when the answer arrives to an empty room.
///
/// The form is the popover's git section today and the board settings sheet once that exists
/// the ruling's container moved in the 2026-07-31 popover/sheet split, its substance did not, and
/// `noteFormVisible(_:)` is the one line the sheet will re-point.
/// The form is the **board settings sheet's Git section** (`BoardSettingsSheet`), which is where
/// the 2026-07-31 popover/sheet split put add-git; it was the popover's git section until that
/// sheet was built, and `noteFormVisible(_:)` is the one line the move re-pointed the ruling's
/// container changed, its substance did not.
public private(set) var lastFailure: GitOperationFailure?
/// Whether the form add-git was asked from is on screen right now (`noteFormVisible(_:)`).
@@ -114,15 +115,15 @@ public final class HistoryStore {
// MARK: - Commit identity
/// **What repo-local `.git/config` says right now** the popover's two fields, as values rather
/// than as a resolved identity (06 Interaction with external writers: "The board popover's git
/// section exposes name/email fields that write that repo-local config the setting *is* the
/// file").
/// **What repo-local `.git/config` says right now** the settings sheet's two fields, as values
/// rather than as a resolved identity (06 Interaction with external writers: "The board settings
/// sheet's identity section exposes name/email fields that write that repo-local config the
/// setting *is* the file").
///
/// Empty means the file names no such key, which is what an empty field means: the derived default
/// applies, shown as the field's *placeholder*. Filling the field in with the derived value would
/// be the app writing its own guess into the user's repository the first time they edited anything
/// else in the popover the exact thing 06 rules out.
/// else on the sheet the exact thing 06 rules out.
public private(set) var identityName = ""
public private(set) var identityEmail = ""
@@ -132,12 +133,12 @@ public final class HistoryStore {
/// Deliberately not computed at composition: `GitIdentity.derivedDefault()` reads
/// `ProcessInfo.hostName`, which can block on a machine whose name resolution is slow, and the
/// board-open path is where 02-architecture.md's hang-avoidance doctrine is strictest. It is read
/// off the main actor with the config, when the popover asks.
/// off the main actor with the config, when the sheet asks.
public private(set) var derivedIdentity: GitIdentity?
/// The last identity-write failure, surfaced as an inline caption in the popover beside the fields
/// 06's popover-anchored posture ("the user asked from a form still under their eye"), which is
/// exactly where `lastFailure` above already puts add-git's.
/// The last identity-write failure, surfaced as an inline caption on the settings sheet beside the
/// fields 06's form-anchored posture ("the user asked from a form still under their eye"), which
/// is exactly where `lastFailure` above already puts add-git's.
public private(set) var identityFailure: GitOperationFailure?
/// The board's write-provenance ledger, held so an add-git flip can build a committer over the
@@ -173,8 +174,8 @@ public final class HistoryStore {
/// **The form appeared or was dismissed.** Dismissal clears the stale inline error, which is the
/// ruling's own sentence ("dismissing the sheet dismisses the stale error, retry is right there").
///
/// A `Bool` rather than a count because there is one such form per board at a time: the popover is
/// built fresh on each open and the settings sheet is modal to its board window.
/// A `Bool` rather than a count because there is one such form per board at a time: the settings
/// sheet is modal to its board window, and opening it dismisses the popover.
public func noteFormVisible(_ visible: Bool) {
isFormVisible = visible
if !visible { lastFailure = nil }
@@ -258,14 +259,18 @@ public final class HistoryStore {
/// **Opt-in init** (06-history-undo.md Rules): initializes a repository at the board root and
/// immediately commits the whole tree as "Initial board state".
///
/// Reachable from one place the board popover's git section under Pro and from nowhere else:
/// Reachable from one place the board settings sheet's Git section under Pro and from nowhere
/// else:
/// "No silent auto-init, ever", a deliberate pivot from the pathfinder, which initialized a repo
/// under every board it opened.
///
/// **It flips the open board's mode immediately**, which is the design's one sanctioned
/// mid-session transition: "clicking it flips the open board into git mode immediately the
/// popover flows straight into the git controls". The flip is commanded, not discovered, which
/// is what distinguishes it from the `git init` a user runs in a terminal under an open board.
/// popover flows straight into the git controls". Since the 2026-07-31 split that flow is one
/// surface further along the sheet's Git section becomes its Branch and Commit Identity
/// sections, and the popover behind it gains the branch line but the immediacy is the same. The
/// flip is commanded, not discovered, which is what distinguishes it from the `git init` a user
/// runs in a terminal under an open board.
///
/// Only mode `none` can be added to. Mode `git` has nothing to add, and a repo-nested board is
/// one the app "leaves strictly alone" no nested repo, ever.
@@ -279,7 +284,7 @@ public final class HistoryStore {
let root = boardRoot
// Off the main actor: `git_repository_init` plus a whole-tree stage and commit is real
// filesystem work, and the popover it was clicked in stays live while it runs.
// filesystem work, and the sheet it was clicked in stays live while it runs.
let outcome = await Task.detached(priority: .userInitiated) {
GitRepository.create(at: root)
}.value
@@ -326,7 +331,8 @@ public final class HistoryStore {
}
/// Reads the current branch name into `branch` the popover's read-only display line, refreshed
/// when the popover opens. A no-op outside git mode.
/// when the popover opens (and when the settings sheet does, whose create control reads the same
/// surface). A no-op outside git mode.
public func refreshBranch() async {
guard mode == .git else { return }
let root = boardRoot
@@ -337,8 +343,8 @@ public final class HistoryStore {
// MARK: - Commit identity
/// Reads repo-local config and the derived default into the popover's fields. A no-op outside git
/// mode, `refreshBranch()`'s rule.
/// Reads repo-local config and the derived default into the settings sheet's fields. A no-op
/// outside git mode, `refreshBranch()`'s rule.
///
/// Both reads run off the main actor: one opens a repository, the other asks the system for the
/// account and host names.