Give the popover git slot the contextual Pro posture
12's settled ruling supersedes the m4 placeholder: on an ordinary board the git section is simply absent — the popover is complete in itself — and on a board carrying an inert .git it shows only the calm one-line note, 'This board has a git history. Lanework Pro works with it.' The detection is a pure one-line seam checking the board root at popover open, deliberately non-live: .git is filtered from the watch by design, so there is no reload to hang a live fact off, and a quiet signpost self-corrects on next open. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -4,10 +4,11 @@ import SwiftUI
|
||||
/// **The board popover** — "the one board-level surface" (03-board-ui.md § Board popover), and the
|
||||
/// widget in the window's titlebar that opens it.
|
||||
///
|
||||
/// Three sections, in the design's own order: the board rename, the embedded style editor aimed at
|
||||
/// the board, and the git slot. The first two are this milestone's; the third is a *reserved place*
|
||||
/// — see `BoardGitSlot` for what m7 grows there and why an honest placeholder beats an absent
|
||||
/// section.
|
||||
/// Two sections, in the design's own order: the board rename and the embedded style editor aimed at
|
||||
/// the board. A third, contextual note joins them only on a board that carries an inert `.git` — see
|
||||
/// `BoardGitNote` for the wording and why base's posture there is a quiet signpost rather than a
|
||||
/// standing section (12-editions.md § Base and `.git`, ruled 2026-07-27, superseding the earlier
|
||||
/// reserved-slot placeholder this file once carried).
|
||||
///
|
||||
/// ### One home, deliberately
|
||||
///
|
||||
@@ -120,7 +121,8 @@ func boardInfoTitlebarAccessory(
|
||||
|
||||
// MARK: - The popover's content
|
||||
|
||||
/// The three sections, one view (03-board-ui.md § Board popover).
|
||||
/// Two sections and, on a `.git`-bearing board, a contextual note — one view (03-board-ui.md §
|
||||
/// Board popover; 12-editions.md § Base and `.git`).
|
||||
///
|
||||
/// Width is the style editor's — 268 points, the number that keeps the Style… popover narrow enough
|
||||
/// to sit beside a card — so the embedded editor lays out here exactly as it does at its other two
|
||||
@@ -130,10 +132,21 @@ struct BoardInfoView: View {
|
||||
let store: BoardStore
|
||||
let recents: StyleRecents
|
||||
|
||||
/// Whether this board carries a `.git` — checked once, off disk, when the view is built (which
|
||||
/// is every time the popover opens, since `BoardInfoWidget` hands `.popover` a fresh instance).
|
||||
/// See `BoardGitNote.hasGitDirectory(at:)` for why a live-updating fact isn't needed here.
|
||||
private let hasGitDirectory: Bool
|
||||
|
||||
/// The style editor brings its own padding, so the sections around it carry the same number by
|
||||
/// hand instead of an outer padding that would double up on it.
|
||||
private let inset: CGFloat = 14
|
||||
|
||||
init(store: BoardStore, recents: StyleRecents) {
|
||||
self.store = store
|
||||
self.recents = recents
|
||||
self.hasGitDirectory = BoardGitNote.hasGitDirectory(at: store.rootURL)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
@@ -155,19 +168,21 @@ struct BoardInfoView: View {
|
||||
StyleEditorView(store: store, recents: recents, target: .board)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
sectionHeader("Git")
|
||||
BoardGitSlot()
|
||||
// Contextual, not standing (12-editions.md, settled 2026-07-27): an ordinary board adds
|
||||
// nothing here at all — no header, no divider, no placeholder — and the popover ends at
|
||||
// Styling, complete in itself. Only a board that actually carries an inert `.git` earns
|
||||
// this closing note.
|
||||
if hasGitDirectory {
|
||||
Divider()
|
||||
BoardGitNote()
|
||||
.padding(inset)
|
||||
}
|
||||
.padding(inset)
|
||||
}
|
||||
.frame(width: 268)
|
||||
}
|
||||
|
||||
/// The section titles, matching the style editor's own headers so the popover reads as one
|
||||
/// surface rather than three borrowed ones.
|
||||
/// surface rather than borrowed ones.
|
||||
private func sectionHeader(_ title: String) -> some View {
|
||||
Text(title)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
@@ -252,31 +267,39 @@ private struct BoardRenameField: View {
|
||||
|
||||
// MARK: - Git
|
||||
|
||||
/// The git section — **a reserved slot, not a feature** (03-board-ui.md § Board popover).
|
||||
/// The contextual git note — **a quiet signpost, not a feature** (12-editions.md § Base and `.git`,
|
||||
/// settled 2026-07-27). Base has no git integration and never will (that is Pro's), so this is not a
|
||||
/// grow-in-place slot the way the old `BoardGitSlot` placeholder was: there is nothing here to grow.
|
||||
/// Base's whole git story is one line, shown only when it is true.
|
||||
///
|
||||
/// Every board is mode-none today, because the app has no git at all yet: there is no detection, no
|
||||
/// repository, and nothing to init. So this renders the one honest thing a mode-none board can say
|
||||
/// and offers its action disabled, rather than hiding the section — an absent section would teach
|
||||
/// that the board popover has two parts, and the design says it has three.
|
||||
/// On an ordinary board `BoardInfoView` never instantiates this type at all — the section is
|
||||
/// *absent*, matching the card window's absent History section (12: "absent, no placeholder"). Only
|
||||
/// a board that carries an inert `.git` (12-editions.md § The inert posture: "any `.git` is inert" —
|
||||
/// base never reads or writes it, whether the board's own or a Pro user's) earns this note, worded
|
||||
/// exactly as 12 rules: an honest explanation of what the folder is, named exactly where the
|
||||
/// question arises, never a standing ad for Pro.
|
||||
///
|
||||
/// m7-git: this slot grows the whole inventory in 11-command-nexus.md § Configuration controls ▸
|
||||
/// Board popover — add-git on mode none, the this-board-lives-inside-a-repository explanation on
|
||||
/// repo-nested boards (06-history-undo.md), branch display/switch/create, the commit-identity
|
||||
/// name/email fields, add/change remote, the credential and SSH-key surfaces (machine key with Copy
|
||||
/// and Verify, key import, the per-host picker, confirm-gated regeneration), the
|
||||
/// Authentication-needed badge, and ahead/behind with Pull/Push and push-on-commit
|
||||
/// (07-sync-collab.md). The mode-aware branching starts here, where this placeholder is.
|
||||
private struct BoardGitSlot: View {
|
||||
/// Not `private`: `hasGitDirectory(at:)` is the pure seam `BoardInfoPopoverTests.swift` pins directly
|
||||
/// (a fixture board with `.git` → true, without → false), which needs it visible past this file even
|
||||
/// though nothing outside `BoardInfoPopover.swift` calls it in the app itself.
|
||||
struct BoardGitNote: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("This board has no repository. Version history, undo, and sync arrive with Lanework's git integration.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Text("This board has a git history. Lanework Pro works with it.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
Button("Add Git…") {}
|
||||
.disabled(true)
|
||||
}
|
||||
/// Whether `boardRoot` carries a `.git` entry — base's entire detection story, and a deliberately
|
||||
/// small one: nothing in `BoardStore`, `BoardModel`, or `BoardLoader` tracks this as a live fact
|
||||
/// today, because nothing needs it to be live. `FolderWatcher` filters `.git` out of the folder
|
||||
/// watch by design (§ .git filtering — it exists to ignore git churn), so there is no reload
|
||||
/// event this could hang off even if it wanted to; a plain, read-only `FileManager` check taken
|
||||
/// once, at the moment the popover is built, is the honest amount of machinery for a single quiet
|
||||
/// line. A `.git` added or removed while the popover happens to be open is stale until the next
|
||||
/// open — a gap this note's own posture makes harmless, since it is a signpost, not a control.
|
||||
static func hasGitDirectory(at boardRoot: URL) -> Bool {
|
||||
FileManager.default.fileExists(atPath: boardRoot.appendingPathComponent(".git").path)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user