Skip to content

Commit d40da30

Browse files
gi11esclaude
andauthored
fix: suppress unseen badges for Claude tabs restored during startup (#63)
When Deckard restarts, resumed Claude sessions may fire processing events (thinking → stop) before session-start arrives. Since most tabs aren't visible during restore, this incorrectly marks them as completedUnseen (unvisited). Add a suppressUnseen flag set on Claude tabs created during restore, checked in updateBadgeToIdleOrUnseen, and cleared when hook.session-start fires (meaning the tab has been fully restored). Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
1 parent cbfa374 commit d40da30

File tree

2 files changed

+9
-2
lines changed

2 files changed

+9
-2
lines changed

Sources/Detection/HookHandler.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class HookHandler {
1111

1212
case "hook.session-start":
1313
if let surfaceId = message.surfaceId {
14+
// Tab is now restored — re-enable unseen tracking.
15+
windowController?.tabForSurfaceId(surfaceId)?.suppressUnseen = false
1416
windowController?.updateBadge(forSurfaceId: surfaceId, state: .waitingForInput)
1517
windowController?.revealClaudeTab(surfaceId: surfaceId)
1618
// Capture the real session ID from Claude Code

Sources/Window/DeckardWindowController.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class TabItem {
2020
var isClaude: Bool
2121
var sessionId: String?
2222
var badgeState: BadgeState = .none
23+
/// Set during restore — suppresses completedUnseen until hook.session-start fires.
24+
var suppressUnseen: Bool = false
2325

2426
enum BadgeState: String {
2527
case none
@@ -647,6 +649,9 @@ class DeckardWindowController: NSWindowController, NSSplitViewDelegate {
647649
let tab = TabItem(surface: surface, name: tabName, isClaude: isClaude)
648650
surface.tabId = tab.id
649651
tab.badgeState = isClaude ? .idle : .terminalIdle
652+
if isClaude && isRestoring {
653+
tab.suppressUnseen = true
654+
}
650655
var envVars: [String: String] = [:]
651656
if isClaude {
652657
tab.sessionId = sessionIdToResume
@@ -1141,9 +1146,9 @@ class DeckardWindowController: NSWindowController, NSSplitViewDelegate {
11411146
let visible = isTabVisible(surfaceIdStr)
11421147
let idleState: TabItem.BadgeState = isClaude ? .waitingForInput : .terminalIdle
11431148
let unseenState: TabItem.BadgeState = isClaude ? .completedUnseen : .terminalCompletedUnseen
1144-
let newState = (wasBusy && !visible) ? unseenState : idleState
1149+
let newState = (wasBusy && !visible && !tab.suppressUnseen) ? unseenState : idleState
11451150
DiagnosticLog.shared.log("badge",
1146-
"updateBadgeToIdleOrUnseen: surfaceId=\(surfaceIdStr) wasBusy=\(wasBusy) visible=\(visible) -> \(newState)")
1151+
"updateBadgeToIdleOrUnseen: surfaceId=\(surfaceIdStr) wasBusy=\(wasBusy) visible=\(visible) suppress=\(tab.suppressUnseen) -> \(newState)")
11471152
tab.badgeState = newState
11481153
rebuildSidebar()
11491154
rebuildTabBar()

0 commit comments

Comments
 (0)