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
+2
View File
@@ -53,6 +53,8 @@ One **style editor** component — a background palette grid and a curated symbo
## Board popover
**Restructure 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 the settings surface for one aspect of board configuration, each to be designed in its own dedicated session (all three are empty placeholders until then). The bullets below describe the popover's pre-tab content; what of it rehomes into which tab — and where the embedded style editor lands — is those sessions' to settle. The rulings inside the bullets (rename semantics, the git postures, the popover/sheet split) stand; only their placement is in motion.
The window-title widget opens the **board popover** — the one board-level surface, hosting:
- **Board rename** (settled: this function stays in-app, unlike the pathfinder which dropped it with the inspector). Rename edits the board's frontmatter `title` only — the folder is never renamed by the app; the Finder document name is Finder's to change (01-storage-format.md's board-naming rule). A foreign rename landing while the popover is open resyncs the field from the snapshot only while the field is unfocused — a focused field keeps the user's keystrokes, the dirty-buffer courtesy applied here (settled).
+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