Skip to content

Commit 1555697

Browse files
committed
Add dock badge count for agent statuses needing attention
Show a numeric badge on the dock icon for worktrees with .permission (Input) or .review (Done) agent statuses. Badge updates reactively via Combine and persists until statuses are acknowledged in the sidebar.
1 parent e849783 commit 1555697

2 files changed

Lines changed: 26 additions & 5 deletions

File tree

macos/Sources/App/macOS/AppDelegate.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import AppKit
2+
import Combine
23
import SwiftUI
34
import UserNotifications
45
import OSLog
@@ -155,6 +156,7 @@ class AppDelegate: NSObject,
155156
private var appearanceObserver: NSKeyValueObservation? = nil
156157

157158
private var userDefaultsObserver: NSObjectProtocol? = nil
159+
private var agentStatusBadgeCancellable: AnyCancellable?
158160

159161
/// Signals
160162
private var signals: [DispatchSourceSignal] = []
@@ -312,16 +314,23 @@ class AppDelegate: NSObject,
312314
// Setup signal handlers
313315
setupSignals()
314316

317+
// Observe agent status changes to update the dock badge count
318+
agentStatusBadgeCancellable = worktrunkStore.$agentStatusByWorktreePath
319+
.receive(on: DispatchQueue.main)
320+
.sink { [weak self] _ in
321+
self?.updateDockBadgeForAgentStatus()
322+
}
323+
315324
switch Ghostty.launchSource {
316325
case .app:
317326
// Don't have to do anything.
318327
break
319-
328+
320329
case .zig_run, .cli:
321330
// Part of launch services (clicking an app, using `open`, etc.) activates
322331
// the application and brings it to the front. When using the CLI we don't
323332
// get this behavior, so we have to do it manually.
324-
333+
325334
// This never gets called until we click the dock icon. This forces it
326335
// activate immediately.
327336
applicationDidBecomeActive(.init(name: NSApplication.didBecomeActiveNotification))
@@ -345,8 +354,8 @@ class AppDelegate: NSObject,
345354
// If we're back manually then clear the hidden state because macOS handles it.
346355
self.hiddenState = nil
347356

348-
// Clear the dock badge when the app becomes active
349-
self.setDockBadge(nil)
357+
// Recalculate dock badge: keep agent status count, clear transient bell badge
358+
self.updateDockBadgeForAgentStatus()
350359

351360
// First launch stuff
352361
if (!applicationHasBecomeActive) {
@@ -877,6 +886,11 @@ class AppDelegate: NSObject,
877886
_ = TerminalController.newTab(ghostty, from: window, withBaseConfig: config)
878887
}
879888

889+
private func updateDockBadgeForAgentStatus() {
890+
let count = worktrunkStore.attentionCount
891+
setDockBadge(count > 0 ? "\(count)" : nil)
892+
}
893+
880894
private func setDockBadge(_ label: String? = "") {
881895
NSApp.dockTile.badgeLabel = label
882896
NSApp.dockTile.display()

macos/Sources/Features/Worktrunk/WorktrunkStore.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ final class WorktrunkStore: ObservableObject {
162162
@Published private var worktreesByRepositoryID: [UUID: [Worktree]] = [:]
163163
@Published private var sessionsByWorktreePath: [String: [AISession]] = [:]
164164
@Published private var gitTrackingByWorktreePath: [String: GitTracking] = [:]
165-
@Published private var agentStatusByWorktreePath: [String: WorktreeAgentStatusEntry] = [:]
165+
@Published private(set) var agentStatusByWorktreePath: [String: WorktreeAgentStatusEntry] = [:]
166166
@Published var isRefreshing: Bool = false
167167
@Published var errorMessage: String? = nil
168168
@Published private(set) var sidebarModelRevision: Int = 0
@@ -250,6 +250,13 @@ final class WorktrunkStore: ObservableObject {
250250
agentStatusByWorktreePath[worktreePath]?.status
251251
}
252252

253+
/// Number of worktrees with statuses that need user attention (.permission or .review).
254+
var attentionCount: Int {
255+
agentStatusByWorktreePath.values.filter {
256+
$0.status == .permission || $0.status == .review
257+
}.count
258+
}
259+
253260
func acknowledgeAgentStatus(for worktreePath: String) {
254261
guard let entry = agentStatusByWorktreePath[worktreePath] else { return }
255262

0 commit comments

Comments
 (0)