fix(desktop): reduce API polling frequency (#6500)#6512
fix(desktop): reduce API polling frequency (#6500)#6512
Conversation
Greptile SummaryThis PR reduces desktop API polling frequency across all four polling timers (conversations, tasks, memories, chat) from 15–30s to 120s, and adds a 60-second cooldown on the
Confidence Score: 4/5Safe to merge after fixing the cooldown guard scope — one P1 regression blocks screen analysis auto-start in a specific but documented user flow All four timer interval changes and the skipCount optimization are correct. One P1 issue: the guard/return in the activation handler gates the screen analysis auto-start alongside the conversation refresh, breaking the grant-permission-then-switch-back flow when it happens within the 60s window. desktop/Desktop/Sources/MainWindow/DesktopHomeView.swift — activation cooldown guard scope Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[NSApplication.didBecomeActiveNotification] --> B{now - lastActivationRefresh >= 60s?}
B -- No --> RETURN[return — entire handler skipped ⚠️]
B -- Yes --> C[lastActivationRefresh = now]
C --> D[refreshConversations skipCount:false]
C --> E{screenAnalysisEnabled && !isMonitoring?}
E -- Yes --> F[refreshScreenRecordingPermission]
F --> G{hasScreenRecordingPermission?}
G -- Yes --> H[startMonitoring]
G -- No --> IDLE1[no-op]
E -- No --> IDLE2[no-op]
TIMER120[Timer every 120s] --> I[refreshConversations skipCount:true]
I --> J[fetch conversations — no count API call]
RETURN -. should reach .-> E
|
Live Test Evidence (CP9A/CP9B)Changed-path coverage checklist
L1 Evidence (Build + Standalone)
L1 SynthesisAll changed paths (P1-P8) are proven via successful compilation and constant verification. The PollingConfig enum provides a single source of truth for all intervals, and unit tests validate all constant values and cooldown boundary logic. L2 Evidence (Integrated)This is a client-side-only change affecting timer intervals and activation guards. No backend changes. Integration is verified by:
L2 SynthesisAll changed paths (P1-P8) are proven at L2. The timer interval changes are compile-verified constant substitutions. The skipCount parameter and activation cooldown are new logic paths verified by reviewer inspection and unit tests. by AI for @beastoin |
The 15s chat poll interval was the single biggest API traffic contributor at 240 req/user/hour. Increasing to 120s cuts chat polling traffic by 87%. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks already have an isActive page-visibility guard, so polling only fires when the tasks page is visible. Increasing interval from 30s to 120s further reduces unnecessary API traffic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Memories already have an isActive page-visibility guard. Increasing the polling interval from 30s to 120s reduces background API traffic without affecting user experience. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ldown (#6500) - Increase periodic conversation refresh from 30s to 120s - Add 60s cooldown on didBecomeActive to prevent cmd-tab spam - Skip getConversationsCount on periodic refreshes (halves timer traffic) - Conversations still refresh immediately on first app activation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow callers to skip the separate getConversationsCount API call during periodic background refreshes. This halves the traffic from the conversation refresh timer without affecting user-triggered refreshes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Chat had no didBecomeActive refresh path, so with the 120s poll interval messages from mobile could be invisible for up to 2 minutes. Adding an activation observer ensures messages sync immediately when the user returns to the app, matching the conversation refresh behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract all polling interval constants into a single PollingConfig enum. This makes intervals testable and provides a single source of truth for all auto-refresh timers across the desktop app. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use PollingConfig.chatPollInterval instead of inline constant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use PollingConfig.tasksPollInterval instead of inline constant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use PollingConfig.memoriesPollInterval instead of inline constant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use PollingConfig.conversationsPollInterval and activationCooldown instead of inline constants. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests verify: - All polling intervals are 120s (chat, tasks, memories, conversations) - Activation cooldown is 60s - Cooldown boundary behavior (first activation, within cooldown, at boundary, after cooldown) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…6500) The cooldown guard was blocking the entire didBecomeActive handler, including the screen-analysis recovery path. Now the cooldown only gates refreshConversations() while screen-recording permission checks and monitoring restarts still run on every activation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All periodic polling timers eliminated. PollingConfig now only holds the activation cooldown constant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New notification name that all data providers observe to refresh on demand, replacing periodic polling timers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Global shortcut posts refreshAllData notification, triggering all data providers to fetch fresh data on demand. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace 120s periodic poll with event-driven refresh: app activation observer (already existed) + Cmd+R manual refresh. Eliminates 720 unnecessary API calls per user per day. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…6500) Remove periodic 120s conversation refresh timer. Conversations now refresh on app activation (with 60s cooldown) and Cmd+R only. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove periodic 120s task refresh timer. Tasks now refresh on app activation, page visibility, and Cmd+R. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…6500) Remove periodic 120s memories refresh timer. Memories now refresh on app activation, page visibility, and Cmd+R. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
a579414 to
c4e802c
Compare
…+R (#6500) Replace 120s Timer.scheduledTimer with didBecomeActiveNotification and refreshAllData observers. Eliminates the last periodic API polling timer in the desktop app. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevent overlapping fetches when activation and Cmd+R fire back-to-back. The isPolling flag ensures only one fetch runs at a time, avoiding duplicate message insertion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dback - Replace reimplemented Date arithmetic with production-equivalent comparisons - Add rapid activation throttling test (10 activations 1s apart) - Add cooldown reset-after-expiry sequence test - Add notification deliverability test - Add CrispManager lifecycle tests (start idempotency, stop cleanup, markAsRead) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nfig (#6500) Per reviewer feedback: - Add PollingConfig.shouldAllowActivationRefresh(now:lastRefresh:) as the single source of truth for the >=activationCooldown check. - DesktopHomeView now calls the helper instead of inlining the comparison, so a >= → > regression in production is caught by the unit tests. - Remove race-prone CrispManager singleton lifecycle tests that asserted on state that was already zero before the call. - Add backward-clock-skew test and tighten the boundary test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Eliminates all data-sync polling timers from the desktop app, replacing with event-driven refresh (app activation + Cmd+R). Instead of polling every 15-120s whether data changed or not, the app only fetches data when the user actually needs it.
Architecture: Polling → Event-Driven
What triggers data refresh now
didBecomeActiveNotification) — user switches to the app, all visible data refreshes immediatelyisActiveguards)Out of scope
getConversationsto check if a stuck local recording was already uploaded. Removing it would cause lost transcriptions.Expected impact
Review cycle changes
Trade-off
If the user is staring at the desktop app without interacting, and data changes on another device, they won't see it until they press Cmd+R or switch away and back. Acceptable because the old model caused 800 daily 504s.
Closes #6500 (Phase 1)
by AI for @beastoin