The board popover goes tabbed — Info, Background, Git, three empty rooms awaiting their own sessions

The symbol/name header keeps the top; below it a segmented tab bar replaces
the styling embed and the git section, both parked in-file — their seams are
settled, test-pinned design, and the tab sessions rehome surfaces, not rulings.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-08-07 13:34:23 -04:00
parent 8aefaf23ce
commit c21c53be9c
2 changed files with 74 additions and 27 deletions
+72 -27
View File
@@ -4,11 +4,14 @@ 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.
///
/// 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).
/// **Restructuring in progress (2026-08-07): the popover is going tabbed.** The symbol/name header
/// stays at the top; below it sit three tabs **Info**, **Background**, **Git** each a settings
/// surface for one aspect of board configuration, each deliberately *empty* today: their contents
/// are to be settled one per dedicated design session. The former body the embedded style editor
/// and the mode-aware git section is unrendered for the interim but parked in this file (see the
/// "Parked" marks below), because its pure seams (`BoardGitSection`, the posture notes,
/// `BoardSettingsAvailability`'s caller) are settled design and will rehome into the tabs as those
/// sessions rule.
///
/// ### One home, deliberately
///
@@ -250,14 +253,30 @@ func boardInfoTitlebarAccessory(
return controller
}
// MARK: - Tabs
/// The popover's three aspects, one tab each (03-board-ui.md § Board popover, the 2026-08-07 tab
/// restructure): **Info**, **Background**, **Git**. All three are placeholders empty on purpose
/// until each gets its dedicated design session; the enum exists now so the popover's shape is the
/// tabs' from day one and each session only has to fill its case in.
enum BoardInfoTab: String, CaseIterable, Identifiable {
case info = "Info"
case background = "Background"
case git = "Git"
var id: Self { self }
}
// MARK: - The popover's content
/// 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`).
/// The symbol/name header, then the tab bar, then the selected tab's surface one view
/// (03-board-ui.md § Board popover).
///
/// 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
/// anchors rather than being stretched by a container with its own opinion.
/// Width is the style editor's the number that keeps the Style popover narrow enough to sit
/// beside a card kept through the restructure so the popover's footprint doesn't wander while the
/// tabs are placeholders; whether the tabbed surface wants its own width is each tab session's
/// question to raise.
struct BoardInfoView: View {
let store: BoardStore
@@ -272,6 +291,12 @@ struct BoardInfoView: View {
/// handed the widget's binding to put it down.
@Environment(\.dismiss) private var dismiss
/// The selected tab. `@State` on the popover's content, which `BoardInfoWidget` hands `.popover`
/// fresh on every open so the selection resets to Info per open. Whether the popover should
/// instead remember its last tab is a question for the tab sessions, once the tabs have content
/// worth returning to.
@State private var tab: BoardInfoTab = .info
/// 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.
@@ -335,25 +360,36 @@ struct BoardInfoView: View {
Divider()
VStack(alignment: .leading, spacing: 0) {
sectionHeader("Styling")
.padding(.horizontal, inset)
.padding(.top, inset)
// Always the board, whatever is selected. The S anchor is the selection-aware one
// ("nothing selected = the board"); this embed is the surface that exists *because*
// the board is a style target, so it can have no other target (§ Styling
// Controls: "the board popover's target is the board itself").
// No symbol section the inline `SymbolPicker` beside the rename field above is
// the board glyph's one surface in this popover.
StyleEditorView(store: store, recents: recents, target: .board, showsSymbols: false)
// The tab bar: a segmented control rather than a `TabView`, because the popover is a
// compact settings surface and the segmented idiom is the macOS shape for switching
// between a handful of peer panes inside one. The label is hidden visually but stays
// the control's accessibility name.
Picker("Board configuration", selection: $tab) {
ForEach(BoardInfoTab.allCases) { tab in
Text(tab.rawValue)
}
}
.pickerStyle(.segmented)
.labelsHidden()
.padding(.horizontal, inset)
.padding(.top, inset)
// Contextual, not standing (12-editions.md, settled 2026-07-27): an ordinary free-tier
// board adds nothing here at all no header, no divider, no placeholder and the
// popover ends at Styling, complete in itself. What a Pro board adds instead is the
// mode-aware git section (03-board-ui.md Board popover), which is a *section*, header
// and all, because under Pro git is a feature of the board rather than a signpost.
gitSection
// Each tab's surface empty today, on purpose: the contents are each their own design
// session's to settle (the file-top note). The switch is already the shape those
// sessions will fill in, and the fixed placeholder height is exactly that a
// placeholder, so the popover reads as a surface awaiting content rather than a
// collapsed sliver; it goes the moment any tab has real content to size itself by.
Group {
switch tab {
case .info:
EmptyView()
case .background:
EmptyView()
case .git:
EmptyView()
}
}
.frame(maxWidth: .infinity, minHeight: 120)
}
// The style editor's popover width, taken from the editor rather than restated: the embed
// below must lay out here exactly as it does at its other two anchors, and that number is
@@ -361,6 +397,15 @@ struct BoardInfoView: View {
.frame(width: StyleEditorLayout.popover(bodyPointSize: CardWindowMetrics.bodyPointSize).width)
}
// MARK: Parked pending the tab sessions (2026-08-07)
//
// Nothing below this mark renders today. The style-editor embed left the body with the tab
// restructure (its Background-tab fate is that session's), and the git section postures,
// notes, and the Board Settings row waits here for the Git tab's session. Parked rather
// than deleted because every seam it hangs on is settled, test-pinned design
// (`BoardGitSectionTests`, `BoardSettingsAvailabilityTests`), and the tab sessions rehome
// surfaces, not rulings.
/// The popover's closing section, whichever of the six postures this board is in see
/// `BoardGitSection`.
@ViewBuilder