diff --git a/DESIGN/03-board-ui.md b/DESIGN/03-board-ui.md index 2a183e0..45631ed 100644 --- a/DESIGN/03-board-ui.md +++ b/DESIGN/03-board-ui.md @@ -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). diff --git a/Kanban/UI/Board/BoardInfoPopover.swift b/Kanban/UI/Board/BoardInfoPopover.swift index 7e675e9..e7a2296 100644 --- a/Kanban/UI/Board/BoardInfoPopover.swift +++ b/Kanban/UI/Board/BoardInfoPopover.swift @@ -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