The git surfaces leave the glass — tab, trail, branch line, and the remote pair, peeled
Step 3 of strategy/01-git-excision.md: the popover strip is Info/Theme/Sync, the titlebar widget says the name alone, the card window's History section and its slot go, Board ▸ Pull/Push comes out with the RemoteCommands scaffold, and View ▸ History re-tags from the commit trail to the deferred foreign-change journal. The Sync placeholder re-annotates to the future ops-based sync service. Four git test suites leave with the surfaces they pinned (BoardGitSetup, BoardInfoPopover, BranchSwitch, GitUndo). The git engine still compiles underneath, unreferenced by UI. 2,890 tests green. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -4,21 +4,17 @@ 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.
|
||||
///
|
||||
/// **Tabbed since 2026-08-07 — the restructure is complete, all three tab sessions settled.** The
|
||||
/// symbol/name header stays at the top; below it sit the tabs — **Info**, **Theme**, **Git** — each
|
||||
/// the settings surface for one aspect of board configuration, each settled in its own dedicated
|
||||
/// design session: `BoardInfoTabView`, the metrics dossier; `BoardThemeTabView`, the Solid color /
|
||||
/// Pattern picker (the Background tab's original name, before the same session widened it past the
|
||||
/// generated-only picker and folded manual styling back out to Style… ⌥⌘S); and `BoardGitTabView`,
|
||||
/// which the pre-tab body's mode-aware git section rehomed into whole — postures and notes, none of
|
||||
/// them re-ruled by the move. That tab carried a **Board Settings…** row to a separate sheet until
|
||||
/// later the same day, when the 2026-07-31 popover/sheet split was reversed and the sheet's contents
|
||||
/// rehomed into the tab (see below).
|
||||
/// **Tabbed since 2026-08-07.** The symbol/name header stays at the top; below it sit the tabs —
|
||||
/// **Info**, **Theme**, **Sync** — each the settings surface for one aspect of board configuration,
|
||||
/// each settled in its own dedicated design session: `BoardInfoTabView`, the metrics dossier;
|
||||
/// `BoardThemeTabView`, the Solid color / Pattern picker (the Background tab's original name, before
|
||||
/// the same session widened it past the generated-only picker and folded manual styling back out to
|
||||
/// Style… ⌥⌘S); and `BoardSyncTabView`, a standing placeholder for the future ops-based sync service.
|
||||
/// The Git tab that once sat here left with the git excision (strategy/01-git-excision.md,
|
||||
/// 2026-08-08).
|
||||
///
|
||||
/// **Tab membership is the git posture's**, and **selection resets to Info on every open** — both
|
||||
/// the Git session's rulings. Since 12-editions.md ▸ PIVOT 2026-08-07 the first of those is a
|
||||
/// structural rule with nothing left to exclude: every board carries all three tabs (03 ▸ Board
|
||||
/// popover, the same-day pivot note) — see `BoardInfoTab`.
|
||||
/// **Selection resets to Info on every open** — the tab session's ruling, unaffected by the git
|
||||
/// excision: see `BoardInfoTab`.
|
||||
///
|
||||
/// ### One home, deliberately
|
||||
///
|
||||
@@ -29,11 +25,9 @@ import SwiftUI
|
||||
///
|
||||
/// **And it is the board's only configuration home** (ruled 2026-08-07, reversing the 2026-07-31
|
||||
/// popover/sheet split): setup briefly lived in a board settings sheet with its own menu command and
|
||||
/// a row here pointing at it. The sheet is retired, its add-git and commit-identity controls render
|
||||
/// inline in the Git tab's postures (`BoardGitSetup.swift`), branch creation went back into the
|
||||
/// switch menu (`BoardGitControls`), and Board ▸ Board Settings… left the menu bar with them. So
|
||||
/// "one home per control" — the split's own promise — is now satisfied by there being one surface,
|
||||
/// and ⌘I is the door to all of it.
|
||||
/// a row here pointing at it. The sheet is retired and Board ▸ Board Settings… left the menu bar with
|
||||
/// it. So "one home per control" — the split's own promise — is now satisfied by there being one
|
||||
/// surface, and ⌘I is the door to all of it.
|
||||
|
||||
// MARK: - Presentation state
|
||||
|
||||
@@ -74,17 +68,13 @@ extension FocusedValues {
|
||||
|
||||
// MARK: - The window-title widget
|
||||
|
||||
/// The titlebar widget: the board's glyph beside a two-line identity block — its name, and on a
|
||||
/// git-mode board its branch under the name — with a trailing disclosure chevron, whose one job is
|
||||
/// this popover.
|
||||
/// The titlebar widget: the board's glyph beside its name, with a trailing disclosure chevron, whose
|
||||
/// one job is this popover.
|
||||
///
|
||||
/// **A two-line stack since 2026-08-07** (03-board-ui.md ▸ Board popover, the window-title widget
|
||||
/// passage). It was one line reading `glyph Title — branch ⌄`, and the em-dash was the tell: a
|
||||
/// separator doing a *hierarchy's* job, with the branch competing for the same width as the name it
|
||||
/// qualifies. So the branch moved under the title in its own smaller, secondary line, the em-dash
|
||||
/// retired, and the glyph grew to span both lines — an icon sized to the block it labels rather than
|
||||
/// to whichever line it happened to sit on. A board with no branch is the single title line, vertically
|
||||
/// centred beside the same glyph, which is the same block with one row.
|
||||
/// **Was a two-line stack** (03-board-ui.md ▸ Board popover, the window-title widget passage): a
|
||||
/// git-mode board's branch sat under the name in its own smaller, secondary line. The branch line
|
||||
/// left with app-managed git (strategy/01-git-excision.md, 2026-08-08); the widget is back to the
|
||||
/// single title line, vertically centred beside the glyph.
|
||||
///
|
||||
/// **The popover is anchored to the widget itself** — it hangs from the button rather than from the
|
||||
/// window or the board — which is what makes the affordance and the surface read as one thing. A
|
||||
@@ -93,37 +83,25 @@ extension FocusedValues {
|
||||
/// only the *placement* (`boardInfoTitlebarAccessory`).
|
||||
///
|
||||
/// **Whole-area clickable, not just the chevron** (the card that widened this from a 20×18 chevron
|
||||
/// button to the full name/branch/chevron button): the title and branch strings sit inside the same
|
||||
/// `Button`, so a click anywhere across the board's name — or its branch, when shown — opens the
|
||||
/// popover exactly as a click on the chevron always has.
|
||||
/// button to the full name/chevron button): the title string sits inside the same `Button`, so a
|
||||
/// click anywhere across the board's name opens the popover exactly as a click on the chevron
|
||||
/// always has.
|
||||
struct BoardInfoWidget: View {
|
||||
|
||||
let store: BoardStore
|
||||
let recents: StyleRecents
|
||||
|
||||
/// The git state this board's **session** composed with — read once, at the moment the widget is
|
||||
/// installed, and never re-derived (12-editions.md ▸ The entitlement: "a lapse never interrupts
|
||||
/// an open session"). `git` is a reference type and `@Observable`, so add-git flipping the mode,
|
||||
/// or a branch switch, redraws the widget without anything here being re-created.
|
||||
///
|
||||
/// It stopped being a tier signal at 12 ▸ PIVOT 2026-08-07 — every session composes a git state
|
||||
/// now, whatever the tier — so a `nil` here means only "this caller had no session to hand over"
|
||||
/// (previews, the accessory-installation tests), which reads as mode `none`.
|
||||
let git: HistoryStore?
|
||||
|
||||
@Bindable var presentation: BoardInfoPresentation
|
||||
|
||||
/// The widget's two strings, computed fresh on every body evaluation rather than cached anywhere.
|
||||
/// That matters here specifically: `boardInfoTitlebarAccessory` builds this view exactly **once**
|
||||
/// at install, so a value read anywhere but inside `body` would freeze at the widget's birth and
|
||||
/// never see a later rename or branch switch. `store.snapshot` and `git.branch` are both
|
||||
/// `@Observable`, so reading them here is what makes the title and branch live.
|
||||
/// The widget's title string, computed fresh on every body evaluation rather than cached
|
||||
/// anywhere. That matters here specifically: `boardInfoTitlebarAccessory` builds this view
|
||||
/// exactly **once** at install, so a value read anywhere but inside `body` would freeze at the
|
||||
/// widget's birth and never see a later rename. `store.snapshot` is `@Observable`, so reading it
|
||||
/// here is what makes the title live.
|
||||
private var summary: BoardInfoTitlebarSummary {
|
||||
BoardInfoTitlebarSummary(
|
||||
snapshotTitle: store.snapshot.title.value,
|
||||
rootURL: store.rootURL,
|
||||
mode: git?.mode ?? .none,
|
||||
branch: git?.branch
|
||||
rootURL: store.rootURL
|
||||
)
|
||||
}
|
||||
|
||||
@@ -151,33 +129,17 @@ struct BoardInfoWidget: View {
|
||||
// everything VoiceOver needs (`accessibilityLabel` below).
|
||||
.accessibilityHidden(true)
|
||||
|
||||
// The identity block: the name, and the branch beneath it on a git-mode board. On
|
||||
// any other board this is the single title line and the `HStack`'s own centring puts
|
||||
// it level with the glyph — the stack is the same shape with one row, never a
|
||||
// special case.
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(summary.title)
|
||||
// Styled like a titlebar title, because that is what it now stands in for
|
||||
// (`BoardWindowHost` hides the system title display in favor of this widget).
|
||||
.font(.system(size: 13, weight: .semibold))
|
||||
.foregroundStyle(.primary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
if let branch = summary.branch {
|
||||
// Smaller and secondary — the qualifier under the name it qualifies. The
|
||||
// em-dash that separated the two on one line retired with the stack: a
|
||||
// hierarchy that a layout can state does not need punctuation to state it.
|
||||
Text(branch)
|
||||
.font(.system(size: 11))
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
}
|
||||
}
|
||||
// Yields space to the chevron first when the block doesn't fit inside the width cap
|
||||
// below — the board's identity is the more load-bearing half of the pair, and both
|
||||
// its lines truncate rather than the disclosure disappearing.
|
||||
// The identity block: the board's name. The `HStack`'s own centring puts it level
|
||||
// with the glyph.
|
||||
Text(summary.title)
|
||||
// Styled like a titlebar title, because that is what it now stands in for
|
||||
// (`BoardWindowHost` hides the system title display in favor of this widget).
|
||||
.font(.system(size: 13, weight: .semibold))
|
||||
.foregroundStyle(.primary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
// Yields space to the chevron first when the title doesn't fit inside the width cap
|
||||
// below — the board's identity is the more load-bearing half of the pair.
|
||||
.layoutPriority(1)
|
||||
|
||||
Image(systemName: "chevron.down")
|
||||
@@ -186,11 +148,9 @@ struct BoardInfoWidget: View {
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.font(.system(size: 13))
|
||||
// A long board name (or branch) must not swallow the whole titlebar — capped rather
|
||||
// than left to grow, with the truncation above doing the rest. The height is the
|
||||
// two-line block's (2026-08-07; it was the original chevron's 18 while the widget was
|
||||
// one line), which is what keeps the accessory titlebar-appropriate: tall enough for
|
||||
// name-over-branch, and no taller than a standard title bar carries.
|
||||
// A long board name must not swallow the whole titlebar — capped rather than left to
|
||||
// grow, with the truncation above doing the rest. The height keeps the accessory
|
||||
// titlebar-appropriate: no taller than a standard title bar carries.
|
||||
.frame(maxWidth: 400, alignment: .leading)
|
||||
.frame(height: 32)
|
||||
.contentShape(Rectangle())
|
||||
@@ -199,24 +159,16 @@ struct BoardInfoWidget: View {
|
||||
.help("Board Info")
|
||||
.accessibilityLabel(accessibilityLabel)
|
||||
.accessibilityHint("Shows board info")
|
||||
// Populates the branch line the moment a git-mode board's window opens, rather than waiting
|
||||
// on the popover's own read (`BoardGitControls`'s `.task`, which only runs once the popover
|
||||
// has actually been opened once). The widget is on screen from the start, so it is the
|
||||
// earlier honest place to ask; `refreshBranch()` is already a no-op outside git mode, so this
|
||||
// costs nothing on the other four postures.
|
||||
.task { await git?.refreshBranch() }
|
||||
.popover(isPresented: $presentation.isPresented, arrowEdge: .bottom) {
|
||||
BoardInfoView(store: store, recents: recents, git: git)
|
||||
BoardInfoView(store: store, recents: recents)
|
||||
}
|
||||
}
|
||||
|
||||
/// What VoiceOver reads for the button, now that it says more than "Board Info": the board's
|
||||
/// name, plus the branch when the widget is showing one — `.help` keeps the shorter "Board Info"
|
||||
/// wording as the hover tooltip, and `.accessibilityHint` on the widget itself still names what
|
||||
/// the button does.
|
||||
/// What VoiceOver reads for the button: the board's name — `.help` keeps the shorter "Board
|
||||
/// Info" wording as the hover tooltip, and `.accessibilityHint` on the widget itself still names
|
||||
/// what the button does.
|
||||
private var accessibilityLabel: String {
|
||||
guard let branch = summary.branch else { return summary.title }
|
||||
return "\(summary.title), branch \(branch)"
|
||||
summary.title
|
||||
}
|
||||
|
||||
/// The widget glyph's tint: the board's `iconColor` where it resolves, the quiet secondary
|
||||
@@ -231,10 +183,9 @@ struct BoardInfoWidget: View {
|
||||
|
||||
// MARK: - The widget's strings
|
||||
|
||||
/// **The window-title widget's two strings, as one pure function** of the board's on-disk title, its
|
||||
/// folder, and the session's git posture — pulled out so the fallback rule and the branch-visibility
|
||||
/// rule are each assertable without a widget on screen (`BoardInfoTitlebarSummaryTests`), the same
|
||||
/// reason `BoardGitSection.resolve` exists over in `BoardGitTabView.swift`.
|
||||
/// **The window-title widget's title string, as one pure function** of the board's on-disk title and
|
||||
/// its folder — pulled out so the fallback rule is assertable without a widget on screen
|
||||
/// (`BoardInfoTitlebarSummaryTests`).
|
||||
///
|
||||
/// **Title.** `AppModel.displayName(of:)` is the same rule applied to the window's actual title
|
||||
/// (`BoardWindowHost.windowTitle` reads it, and `.navigationTitle` keeps feeding it to the Window
|
||||
@@ -245,26 +196,19 @@ struct BoardInfoWidget: View {
|
||||
/// with plain values and no fixture board on disk — the one duplication this card leaves behind
|
||||
/// rather than reshaping `AppModel.displayName(of:)`'s signature to fit both call sites.
|
||||
///
|
||||
/// **Branch.** Shown only when the board is actually git-mode — `mode == .git` with a non-`nil`
|
||||
/// branch — the same condition `BoardGitSection.resolve`'s `.branch` case covers. **The tier clause
|
||||
/// is gone** (12-editions.md ▸ PIVOT 2026-08-07: git is tier-independent, so a git-mode board is a
|
||||
/// git-mode board and the widget says so on every tier); the rule it read `tier == .pro && mode ==
|
||||
/// .git` until that day is recorded here rather than restated in code. A board with no repository or
|
||||
/// an inert one (mode `.none`, `.repoNested`, `.unverifiable`) shows no branch; neither does a
|
||||
/// git-mode board whose branch has not been read yet (`HistoryStore.branch` starts `nil` until
|
||||
/// `refreshBranch()` answers, which the widget's own `.task` kicks off at open).
|
||||
/// **Branch.** The widget once carried a second line — the board's git branch, shown on a git-mode
|
||||
/// board — as a `branch` field here. That line left with app-managed git
|
||||
/// (strategy/01-git-excision.md, 2026-08-08); the summary is the title alone now.
|
||||
struct BoardInfoTitlebarSummary: Equatable {
|
||||
|
||||
let title: String
|
||||
let branch: String?
|
||||
|
||||
init(snapshotTitle: String?, rootURL: URL, mode: BoardGitMode, branch: String?) {
|
||||
init(snapshotTitle: String?, rootURL: URL) {
|
||||
if let snapshotTitle, !snapshotTitle.isEmpty {
|
||||
self.title = snapshotTitle
|
||||
} else {
|
||||
self.title = rootURL.deletingPathExtension().lastPathComponent
|
||||
}
|
||||
self.branch = mode == .git ? branch : nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,21 +220,15 @@ struct BoardInfoTitlebarSummary: Equatable {
|
||||
/// window, removed on detach — for the same reason it owns the delegate proxying: the window is
|
||||
/// SwiftUI's, and anything hung on it has to be taken back off.
|
||||
@MainActor
|
||||
/// `git` defaults to no session, so that a caller with none in hand (the accessory-installation
|
||||
/// tests, which are about AppKit plumbing rather than about git) describes a board honestly rather
|
||||
/// than by accident: a Git tab in its no-repository posture, and a widget with no branch line. The
|
||||
/// app's own call site passes the session's value explicitly.
|
||||
func boardInfoTitlebarAccessory(
|
||||
store: BoardStore,
|
||||
recents: StyleRecents,
|
||||
git: HistoryStore? = nil,
|
||||
presentation: BoardInfoPresentation
|
||||
) -> NSTitlebarAccessoryViewController {
|
||||
let hosting = NSHostingView(
|
||||
rootView: BoardInfoWidget(
|
||||
store: store,
|
||||
recents: recents,
|
||||
git: git,
|
||||
presentation: presentation
|
||||
)
|
||||
)
|
||||
@@ -314,31 +252,22 @@ func boardInfoTitlebarAccessory(
|
||||
// MARK: - Tabs
|
||||
|
||||
/// The popover's aspects, one tab each (03-board-ui.md § Board popover, the 2026-08-07 tab
|
||||
/// restructure — all three original sessions settled): **Info** (`BoardInfoTabView`), **Theme**
|
||||
/// (`BoardThemeTabView`), **Git** (`BoardGitTabView`) — and **Sync** (`BoardSyncTabView`), added
|
||||
/// 2026-08-07 as a standing placeholder: the strip claims the position now, the surface says
|
||||
/// honestly that nothing lives there yet, and 07-sync-collab.md's cards are where its contents get
|
||||
/// ruled. The raw values are the segmented control's own labels, so the strip needs no separate
|
||||
/// label function.
|
||||
/// restructure): **Info** (`BoardInfoTabView`), **Theme** (`BoardThemeTabView`) — and **Sync**
|
||||
/// (`BoardSyncTabView`), added 2026-08-07 as a standing placeholder: the strip claims the position
|
||||
/// now, the surface says honestly that nothing lives there yet, and the future sync-service
|
||||
/// workstream is where its contents get ruled. A fourth tab, Git, sat here until the git excision
|
||||
/// (strategy/01-git-excision.md, 2026-08-08) removed it. The raw values are the segmented control's
|
||||
/// own labels, so the strip needs no separate label function.
|
||||
enum BoardInfoTab: String, CaseIterable, Identifiable {
|
||||
|
||||
case info = "Info"
|
||||
case theme = "Theme"
|
||||
case git = "Git"
|
||||
case sync = "Sync"
|
||||
|
||||
var id: Self { self }
|
||||
|
||||
// **Membership is the git posture's, and the posture never says "absent" any more.** The Git
|
||||
// session ruled (2026-08-07) that the Git tab joins the strip only where `BoardGitSection` has
|
||||
// something true to say, which then meant dropping it on a free board with no `.git` — the
|
||||
// "absent, no placeholder" rule carried up to the strip. **12-editions.md ▸ PIVOT 2026-08-07**,
|
||||
// the same day, retired the free-only postures wholesale: git left the paywall, `.absent` and
|
||||
// `.proPointer` died with it, and 03-board-ui.md ▸ Board popover records the consequence — "the
|
||||
// absent posture is unreachable and every board carries all three tabs. The membership rule
|
||||
// stands structurally — the strip still asks the posture — it just never hears 'absent'
|
||||
// anymore." So there is no `available(…)` filter here to ask it with: membership is `allCases`,
|
||||
// in `allCases`' own order (Info, Theme, Git, Sync), which is what the filter answered anyway.
|
||||
// Membership is simply `allCases`, in `allCases`' own order (Info, Theme, Sync) — every board
|
||||
// carries the whole strip, unconditionally.
|
||||
}
|
||||
|
||||
// MARK: - The popover's content
|
||||
@@ -348,18 +277,16 @@ enum BoardInfoTab: String, CaseIterable, Identifiable {
|
||||
///
|
||||
/// 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 didn't wander while the
|
||||
/// tabs filled in; all three settled tabs (Info, Theme, Git) kept it, so whether the tabbed surface
|
||||
/// tabs filled in; every settled tab (Info, Theme, Sync) kept it, so whether the tabbed surface
|
||||
/// ever wants its own width remains open, but nothing has needed one yet.
|
||||
struct BoardInfoView: View {
|
||||
|
||||
let store: BoardStore
|
||||
let recents: StyleRecents
|
||||
let git: HistoryStore?
|
||||
|
||||
/// The selected tab, and **it resets to Info on every open** — a ruling, not an accident (the
|
||||
/// Git session, 2026-08-07, closing the question the earlier tab sessions deferred): the popover
|
||||
/// is transient and Info is the board's face, and a remembered tab could strand selection on a
|
||||
/// tab the next board's posture doesn't offer. `@State` on the popover's content, which
|
||||
/// The selected tab, and **it resets to Info on every open** — a ruling, not an accident: the
|
||||
/// popover is transient and Info is the board's face, and a remembered tab could strand
|
||||
/// selection on a tab the next board doesn't offer. `@State` on the popover's content, which
|
||||
/// `BoardInfoWidget` hands `.popover` fresh on every open, is exactly that rule and nothing more.
|
||||
@State private var tab: BoardInfoTab = .info
|
||||
|
||||
@@ -373,12 +300,10 @@ struct BoardInfoView: View {
|
||||
|
||||
init(
|
||||
store: BoardStore,
|
||||
recents: StyleRecents,
|
||||
git: HistoryStore? = nil
|
||||
recents: StyleRecents
|
||||
) {
|
||||
self.store = store
|
||||
self.recents = recents
|
||||
self.git = git
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -425,10 +350,8 @@ struct BoardInfoView: View {
|
||||
// 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. It iterates `allCases` — the strip's membership is
|
||||
// still the board's git posture in principle (the Git session's ruling), and since
|
||||
// 12-editions.md ▸ PIVOT 2026-08-07 that posture is never "absent", so every board
|
||||
// carries the whole strip (03-board-ui.md ▸ Board popover, the same-day pivot note).
|
||||
// the control's accessibility name. It iterates `allCases` — every board carries the
|
||||
// whole strip, unconditionally (03-board-ui.md ▸ Board popover).
|
||||
Picker("Board configuration", selection: $tab) {
|
||||
ForEach(BoardInfoTab.allCases) { tab in
|
||||
Text(tab.rawValue)
|
||||
@@ -439,7 +362,7 @@ struct BoardInfoView: View {
|
||||
.padding(.horizontal, inset)
|
||||
.padding(.top, inset)
|
||||
|
||||
// The selected tab's surface — the first three settled 2026-08-07, each in its own
|
||||
// The selected tab's surface — Info and Theme settled 2026-08-07, each in its own
|
||||
// dedicated session and its own file; Sync is that day's standing placeholder. Each pads
|
||||
// itself by `inset`, so the switch adds nothing.
|
||||
switch tab {
|
||||
@@ -447,8 +370,6 @@ struct BoardInfoView: View {
|
||||
BoardInfoTabView(store: store, inset: inset)
|
||||
case .theme:
|
||||
BoardThemeTabView(store: store, inset: inset)
|
||||
case .git:
|
||||
BoardGitTabView(store: store, git: git, inset: inset)
|
||||
case .sync:
|
||||
BoardSyncTabView(inset: inset)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user