Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Packages/CrowCore/Sources/CrowCore/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,20 @@ public final class AppState {
public var reviewRequests: [ReviewRequest] = []
public var isLoadingReviews: Bool = false

public var excludeReviewRepos: [String] = []

public var filteredReviewRequests: [ReviewRequest] {
guard !excludeReviewRepos.isEmpty else { return reviewRequests }
let excludeSet = Set(excludeReviewRepos.map { $0.lowercased() })
return reviewRequests.filter { !excludeSet.contains($0.repo.lowercased()) }
}

/// IDs of review requests the user has already seen (for badge count).
public var seenReviewRequestIDs: Set<String> = []

/// Number of unseen review requests (for sidebar badge).
public var unseenReviewCount: Int {
reviewRequests.filter { !seenReviewRequestIDs.contains($0.id) }.count
filteredReviewRequests.filter { !seenReviewRequestIDs.contains($0.id) }.count
}

/// Whether the VS Code `code` CLI is available on this system.
Expand Down
18 changes: 17 additions & 1 deletion Packages/CrowCore/Sources/CrowCore/Models/AppConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public struct ConfigDefaults: Codable, Sendable, Equatable {
public var cli: String
public var branchPrefix: String
public var excludeDirs: [String]
public var excludeReviewRepos: [String]

/// Characters that are invalid in git ref names (see `git check-ref-format`).
private static let invalidBranchChars = CharacterSet(charactersIn: " ~^:?*[\\")
Expand All @@ -137,12 +138,27 @@ public struct ConfigDefaults: Codable, Sendable, Equatable {
provider: String = "github",
cli: String = "gh",
branchPrefix: String = "feature/",
excludeDirs: [String] = ["node_modules", ".git", "vendor", "dist", "build", "target"]
excludeDirs: [String] = ["node_modules", ".git", "vendor", "dist", "build", "target"],
excludeReviewRepos: [String] = []
) {
self.provider = provider
self.cli = cli
self.branchPrefix = branchPrefix
self.excludeDirs = excludeDirs
self.excludeReviewRepos = excludeReviewRepos
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
provider = try container.decodeIfPresent(String.self, forKey: .provider) ?? "github"
cli = try container.decodeIfPresent(String.self, forKey: .cli) ?? "gh"
branchPrefix = try container.decodeIfPresent(String.self, forKey: .branchPrefix) ?? "feature/"
excludeDirs = try container.decodeIfPresent([String].self, forKey: .excludeDirs) ?? ["node_modules", ".git", "vendor", "dist", "build", "target"]
excludeReviewRepos = try container.decodeIfPresent([String].self, forKey: .excludeReviewRepos) ?? []
}

private enum CodingKeys: String, CodingKey {
case provider, cli, branchPrefix, excludeDirs, excludeReviewRepos
}
}

Expand Down
13 changes: 12 additions & 1 deletion Packages/CrowCore/Tests/CrowCoreTests/AppConfigTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Testing
WorkspaceInfo(name: "TestOrg", provider: "github", cli: "gh", alwaysInclude: ["repo1"]),
WorkspaceInfo(name: "GitLabOrg", provider: "gitlab", cli: "glab", host: "gitlab.example.com"),
],
defaults: ConfigDefaults(provider: "gitlab", cli: "glab", branchPrefix: "fix/", excludeDirs: ["vendor"]),
defaults: ConfigDefaults(provider: "gitlab", cli: "glab", branchPrefix: "fix/", excludeDirs: ["vendor"], excludeReviewRepos: ["zarf-dev/zarf", "bmlt-enabled/yap"]),
notifications: NotificationSettings(globalMute: true),
sidebar: SidebarSettings(hideSessionDetails: true)
)
Expand All @@ -25,6 +25,7 @@ import Testing
#expect(decoded.defaults.provider == "gitlab")
#expect(decoded.defaults.branchPrefix == "fix/")
#expect(decoded.defaults.excludeDirs == ["vendor"])
#expect(decoded.defaults.excludeReviewRepos == ["zarf-dev/zarf", "bmlt-enabled/yap"])
#expect(decoded.notifications.globalMute == true)
#expect(decoded.sidebar.hideSessionDetails == true)
}
Expand All @@ -36,6 +37,7 @@ import Testing
#expect(config.workspaces.isEmpty)
#expect(config.defaults.provider == "github")
#expect(config.defaults.branchPrefix == "feature/")
#expect(config.defaults.excludeReviewRepos.isEmpty)
#expect(config.notifications.globalMute == false)
#expect(config.sidebar.hideSessionDetails == false)
#expect(config.remoteControlEnabled == false)
Expand Down Expand Up @@ -105,6 +107,15 @@ import Testing
#expect(a != c)
}

@Test func configDefaultsDecodeWithoutExcludeReviewRepos() throws {
let json = """
{"defaults": {"provider": "github", "cli": "gh", "branchPrefix": "feature/", "excludeDirs": ["node_modules"]}}
""".data(using: .utf8)!
let config = try JSONDecoder().decode(AppConfig.self, from: json)
#expect(config.defaults.excludeReviewRepos.isEmpty)
#expect(config.defaults.excludeDirs == ["node_modules"])
}

// MARK: - WorkspaceInfo

@Test func workspaceInfoDerivedCLI() {
Expand Down
12 changes: 6 additions & 6 deletions Packages/CrowUI/Sources/CrowUI/ReviewBoardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public struct ReviewBoardView: View {
.background(.background)
.onAppear {
// Mark all current review requests as seen
for request in appState.reviewRequests {
for request in appState.filteredReviewRequests {
appState.seenReviewRequestIDs.insert(request.id)
}
}
Expand All @@ -44,7 +44,7 @@ public struct ReviewBoardView: View {

Spacer()

Text("\(appState.reviewRequests.count) pending")
Text("\(appState.filteredReviewRequests.count) pending")
.font(.caption)
.foregroundStyle(CorveilTheme.textSecondary)
}
Expand All @@ -55,7 +55,7 @@ public struct ReviewBoardView: View {

@ViewBuilder
private var reviewList: some View {
if appState.reviewRequests.isEmpty {
if appState.filteredReviewRequests.isEmpty {
VStack {
Spacer().frame(height: 40)
Image(systemName: "eye.circle")
Expand All @@ -74,7 +74,7 @@ public struct ReviewBoardView: View {
}
.frame(maxWidth: .infinity)
} else {
List(appState.reviewRequests) { request in
List(appState.filteredReviewRequests) { request in
ReviewRow(request: request, appState: appState)
}
.listStyle(.inset)
Expand Down Expand Up @@ -207,8 +207,8 @@ public struct ReviewTerminalsSidebarRow: View {
ProgressView()
.controlSize(.mini)
}
if appState.reviewRequests.count > 0 {
Text("\(appState.reviewRequests.count)")
if appState.filteredReviewRequests.count > 0 {
Text("\(appState.filteredReviewRequests.count)")
.font(.system(size: 10, weight: .semibold))
.monospacedDigit()
.padding(.horizontal, 4)
Expand Down
17 changes: 17 additions & 0 deletions Packages/CrowUI/Sources/CrowUI/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public struct SettingsView: View {
@State var config: AppConfig
@State private var isAddingWorkspace = false
@State private var editingWorkspace: WorkspaceInfo?
@State private var excludeReviewReposText: String

public var onSave: ((String, AppConfig) -> Void)?
public var onRescaffold: ((String) -> Void)?
Expand All @@ -22,6 +23,7 @@ public struct SettingsView: View {
self.appState = appState
self._devRoot = State(initialValue: devRoot)
self._config = State(initialValue: config)
self._excludeReviewReposText = State(initialValue: config.defaults.excludeReviewRepos.joined(separator: ", "))
self.onSave = onSave
self.onRescaffold = onRescaffold
}
Expand Down Expand Up @@ -156,6 +158,21 @@ public struct SettingsView: View {
.foregroundStyle(.secondary)
}

Section("Reviews") {
TextField("Excluded Repos", text: $excludeReviewReposText)
.textFieldStyle(.roundedBorder)
.onChange(of: excludeReviewReposText) { _, _ in
config.defaults.excludeReviewRepos = excludeReviewReposText
.split(separator: ",")
.map { $0.trimmingCharacters(in: .whitespaces) }
.filter { !$0.isEmpty }
save()
}
Text("Comma-separated list of repos to hide from the review board (e.g., zarf-dev/zarf, bmlt-enabled/yap).")
.font(.caption)
.foregroundStyle(.secondary)
}

Section("Remote Control") {
Toggle("Enable remote control for new sessions", isOn: $config.remoteControlEnabled)
.onChange(of: config.remoteControlEnabled) { _, _ in save() }
Expand Down
2 changes: 2 additions & 0 deletions Sources/Crow/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
// be rebuilt to include (or drop) `--rc` before its surface is pre-initialized.
appState.remoteControlEnabled = config.remoteControlEnabled
appState.managerAutoPermissionMode = config.managerAutoPermissionMode
appState.excludeReviewRepos = config.defaults.excludeReviewRepos

// Create session service and hydrate state
let service = SessionService(store: store, appState: appState, telemetryPort: config.telemetry.enabled ? config.telemetry.port : nil)
Expand Down Expand Up @@ -448,6 +449,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
appState.hideSessionDetails = config.sidebar.hideSessionDetails
appState.remoteControlEnabled = config.remoteControlEnabled
appState.managerAutoPermissionMode = config.managerAutoPermissionMode
appState.excludeReviewRepos = config.defaults.excludeReviewRepos
}

// MARK: - Socket Server
Expand Down
7 changes: 6 additions & 1 deletion Sources/Crow/App/IssueTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,14 @@ final class IssueTracker {
reviews[i].reviewSessionID = session.id
}
}
let allCurrentIDs = Set(reviews.map(\.id))
let excludeSet = Set(config.defaults.excludeReviewRepos.map { $0.lowercased() })
if !excludeSet.isEmpty {
reviews = reviews.filter { !excludeSet.contains($0.repo.lowercased()) }
}
let currentIDs = Set(reviews.map(\.id))
let newIDs = currentIDs.subtracting(previousReviewRequestIDs)
previousReviewRequestIDs = currentIDs
previousReviewRequestIDs = allCurrentIDs
if !isFirstFetch && !newIDs.isEmpty {
let newRequests = reviews.filter { newIDs.contains($0.id) }
onNewReviewRequests?(newRequests)
Expand Down
4 changes: 3 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ All persistent state lives under `~/Library/Application Support/crow/` (see `Pac
"provider": "github",
"cli": "gh",
"branchPrefix": "feature/",
"excludeDirs": ["node_modules", ".git", "vendor", "dist", "build", "target"]
"excludeDirs": ["node_modules", ".git", "vendor", "dist", "build", "target"],
"excludeReviewRepos": ["zarf-dev/zarf", "bmlt-enabled/yap"]
}
}
```
Expand All @@ -57,6 +58,7 @@ All persistent state lives under `~/Library/Application Support/crow/` (see `Pac
- **`host`** — set for self-hosted GitLab; exported as `GITLAB_HOST` when invoking `glab`.
- **`branchPrefix`** — used by the `/crow-workspace` skill when creating new branches.
- **`excludeDirs`** — ignored when scanning repos for git worktrees.
- **`excludeReviewRepos`** — repos to hide from the review board (e.g., `["zarf-dev/zarf"]`). Matching reviews are filtered out from the board, sidebar badge count, and notifications.

## Manager Terminal

Expand Down
Loading