Repository navigation
fix: resume suspended polling when screensDidWake is missed - #401
Merged
chattymin merged 1 commit intoOct 5, 2026
Merged
Conversation
Display sleep stopped the poll timer and only screensDidWake brought it back. If that notification never arrived, polling stayed off until a restart, and the empty snapshot written by the midnight day-change refresh (no tokens yet at 00:00) froze the menu bar at 0 with an empty providers list and no error (chattymin#350). While suspended, a 5-minute probe checks the real display state and resumes polling when the screens are awake. didWake and every refresh run the same check, so any trigger heals a stale suspension.
8 of 10 tasks
donky-ey
pushed a commit
to donky-ey/PokeDexBar
that referenced
this pull request
Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the "providers list goes empty after prolonged uptime" report. The fix makes polling recover by itself instead of needing a restart.
Root cause.
UsageStorestops the poll timer onNSWorkspace.screensDidSleepNotification(to save battery) and restarts it only onscreensDidWake. Nothing else could clearpollingSuspended:didWake, network reconnects, and the midnightNSCalendarDayChangedhandler each ran onerefresh(), but none of them rescheduled the timer. So if ascreensDidWakewas ever missed, polling stayed off for the rest of the process lifetime.This also explains the exact symptom. The overnight
NSCalendarDayChangedrefresh runs at 00:00 with displays asleep. No tokens exist yet for the new day, so it correctly writesproviders: []/menuTitle: "0"with an emptylastError. With the timer gone, nothing replaces that snapshot. It stays frozen while Claude Code keeps producing usage, and a restart fixes it becauseinitreschedules the timer. The popover doesn't refresh on open, so it shows the same frozen state.Fix.
CGDisplayIsAsleep(CGMainDisplayID()). If the screens are awake, it resumes polling and refreshes once.didWakeand everyrefresh()entry run the same check. Any trigger (system wake, network reconnect, manual refresh) heals a stale suspension right away instead of waiting for the probe.polling resumed without screensDidWake (<reason>)to~/Library/Logs/PokeTokenBar.log, so a missed wake shows up in field logs.About
usage-cache.json. The reporter saw it as an unreadable binary blob. That's expected: the cache is zlib-compressed JSON (LocalUsageCache.saveIfNeeded), so it isn't related to this bug.Fixes #350
Type of change
Checklist
swift buildandswift testpass locally (scripts/test-gate.sh: 1389 tests, 0 failures, logic-core coverage 93.35%)Test plan
New
UsageStoreTestscases. Display state is injected through the newdisplaysAsleepinit parameter, which defaults toCGDisplayIsAsleep.testSuspendedPollingProbeResumesWhenDisplaysAreAwake: the trigger branch (suspended, screens awake, noscreensDidWake). The probe resumes the timer, removes itself, and the refresh fills the snapshot.testSuspendedPollingProbeStaysSuspendedWhileDisplaysSleep: displays asleep, so polling stays suspended.testSystemWakeResumesSuspendedPollingWhenDisplaysAreAwake: thedidWakepath alone.testRefreshWhileDisplaysAwakeClearsAStaleSuspension: therefresh()path alone, without the probe or wake.testRefreshWhileDisplaysAsleepKeepsPollingSuspended: the midnight refresh with screens off doesn't resume polling.falsemakes the 3 recovery tests fail (9 assertions). The 2 negative tests still pass, as expected.docs/reference/defect-log.md(energy section).