Fix dock tile hang — 11.4K Sentry events (#6194)#6224
Conversation
SidebarView had @ObservedObject CrispManager.shared but never read any CrispManager properties in the view body. Every CrispManager @published change triggered full SidebarView invalidation, cascading to NSDockTile.display() which blocks the main thread for 2+ seconds via synchronous mach_msg to the Dock server (11.4K Sentry events, 199 users). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of incrementing unreadCount per-message in the loop (triggering multiple @published notifications), count new messages first and publish once after the loop. Prevents rapid cascading view invalidations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR resolves a 2s+ main-thread hang (11.4K Sentry events, issue #6194) caused by The fix is two-pronged:
Two minor style observations:
Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Timer
participant CrispManager
participant Backend
participant SidebarView
participant NSDockTile
Note over Timer,NSDockTile: BEFORE fix — per-message @Published writes
Timer->>CrispManager: pollForMessages()
CrispManager->>Backend: fetchUnreadMessages()
Backend-->>CrispManager: [msg1, msg2, msg3]
loop per message
CrispManager->>CrispManager: unreadCount += 1 (@Published)
CrispManager-->>SidebarView: objectWillChange (re-render)
SidebarView->>NSDockTile: display() [sync mach_msg → 2s hang]
end
Note over Timer,NSDockTile: AFTER fix — single batched write, no observer
Timer->>CrispManager: pollForMessages()
CrispManager->>Backend: fetchUnreadMessages()
Backend-->>CrispManager: [msg1, msg2, msg3]
loop per message
CrispManager->>CrispManager: newMessageCount += 1 (local var)
end
CrispManager->>CrispManager: unreadCount += newMessageCount (single write)
Note over SidebarView: @ObservedObject removed — no re-render triggered
|
E2E EvidenceApp: dock-tile-6194 (bundle: Steps Verified
Evidence
by AI for @beastoin |
E2E Evidence — flow-walkerFlow:
Report: https://flow-walker.beastoin.workers.dev/runs/JzzCmCCpJp.html by AI for @beastoin |
CP9 Live Integration Test Evidence — PR #6224L1: Build and run changed component, test standaloneTest app: App launch and stability test
App launched without dock hang: Agent-swift verification (sign-in buttons detected): L1 synthesisThe dock-tile-6194 app built with PR #6224 CrispManager batched update fix launches without dock tile hang. The sign-in screen renders correctly with Apple/Google sign-in buttons, the window accepts click input, and the dock icon remains responsive. The CrispManager.shared.setDockTile batch update logic compiles cleanly and doesn't cause the dock freeze that was reported in Sentry issue #6194 (11.4K events). The fix prevents rapid dock tile updates from stalling the main thread. by AI for @beastoin |
E2E Flow-Walker ReportReport:
by AI for @beastoin |
E2E Flow-Walker Report (re-recorded with auth)Report: https://flow-walker.beastoin.workers.dev/runs/bQtvlGHjaG.html
by AI for @beastoin |
E2E Flow-Walker Report (re-recorded — asserts + dock evidence)Report: https://flow-walker.beastoin.workers.dev/runs/zVfIZRBEfx.html Fixes from Kai's review:
by AI for @beastoin |

Summary
@ObservedObject CrispManagerfrom SidebarView — eliminates cascading view invalidation that triggered synchronousNSDockTile.display()on main threadunreadCountupdates in CrispManager — set final count once after processing all messages instead of incrementing per-message in a loopRoot Cause
CrispManager polling incremented
@Published unreadCountper-message, triggering SidebarView invalidation which cascaded toNSDockTile.display()→ synchronousmach_msgto Dock server → 2s+ main thread hang.Test plan
Closes #6194
🤖 Generated with Claude Code