@@ -119,32 +119,12 @@ func (*App) githubToken(ctx context.Context) (string, error) {
119119 return token , nil
120120}
121121
122- // fetchPRsInternal is the implementation for PR fetching.
123- // It returns GitHub data immediately and starts Turn API queries in the background (when waitForTurn=false),
124- // or waits for Turn data to complete (when waitForTurn=true).
125- func (app * App ) fetchPRsInternal (ctx context.Context , waitForTurn bool ) (incoming []PR , outgoing []PR , err error ) {
126- // Use targetUser if specified, otherwise use authenticated user
127- user := app .currentUser .GetLogin ()
128- if app .targetUser != "" {
129- user = app .targetUser
130- }
131-
132- // Single query to get all PRs involving the user
133- query := fmt .Sprintf ("is:open is:pr involves:%s archived:false" , user )
134-
135- const perPage = 100
136- opts := & github.SearchOptions {
137- ListOptions : github.ListOptions {PerPage : perPage },
138- Sort : "updated" ,
139- Order : "desc" ,
140- }
141-
142- log .Printf ("Searching for PRs with query: %s" , query )
143- searchStart := time .Now ()
144-
122+ // executeGitHubQuery executes a single GitHub search query with retry logic.
123+ func (app * App ) executeGitHubQuery (ctx context.Context , query string , opts * github.SearchOptions ) (* github.IssuesSearchResult , error ) {
145124 var result * github.IssuesSearchResult
146125 var resp * github.Response
147- err = retry .Do (func () error {
126+
127+ err := retry .Do (func () error {
148128 // Create timeout context for GitHub API call
149129 githubCtx , cancel := context .WithTimeout (ctx , 30 * time .Second )
150130 defer cancel ()
@@ -194,19 +174,97 @@ func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incomin
194174 retry .Context (ctx ),
195175 )
196176 if err != nil {
197- return nil , nil , fmt . Errorf ( "search PRs after %d retries: %w" , maxRetries , err )
177+ return nil , err
198178 }
179+ return result , nil
180+ }
199181
200- log .Printf ("GitHub search completed in %v, found %d PRs" , time .Since (searchStart ), len (result .Issues ))
182+ // fetchPRsInternal is the implementation for PR fetching.
183+ // It returns GitHub data immediately and starts Turn API queries in the background (when waitForTurn=false),
184+ // or waits for Turn data to complete (when waitForTurn=true).
185+ func (app * App ) fetchPRsInternal (ctx context.Context , waitForTurn bool ) (incoming []PR , outgoing []PR , err error ) {
186+ // Use targetUser if specified, otherwise use authenticated user
187+ user := app .currentUser .GetLogin ()
188+ if app .targetUser != "" {
189+ user = app .targetUser
190+ }
191+
192+ const perPage = 100
193+ opts := & github.SearchOptions {
194+ ListOptions : github.ListOptions {PerPage : perPage },
195+ Sort : "updated" ,
196+ Order : "desc" ,
197+ }
198+
199+ searchStart := time .Now ()
200+
201+ // Run both queries in parallel
202+ type queryResult struct {
203+ query string
204+ issues []* github.Issue
205+ err error
206+ }
207+
208+ queryResults := make (chan queryResult , 2 )
209+
210+ // Query 1: PRs involving the user
211+ go func () {
212+ query := fmt .Sprintf ("is:open is:pr involves:%s archived:false" , user )
213+ log .Printf ("[GITHUB] Searching for PRs with query: %s" , query )
214+
215+ result , err := app .executeGitHubQuery (ctx , query , opts )
216+ if err != nil {
217+ queryResults <- queryResult {err : err , query : query }
218+ } else {
219+ queryResults <- queryResult {issues : result .Issues , query : query }
220+ }
221+ }()
222+
223+ // Query 2: PRs in user-owned repos with no reviewers
224+ go func () {
225+ query := fmt .Sprintf ("is:open is:pr user:%s review:none archived:false" , user )
226+ log .Printf ("[GITHUB] Searching for PRs with query: %s" , query )
227+
228+ result , err := app .executeGitHubQuery (ctx , query , opts )
229+ if err != nil {
230+ queryResults <- queryResult {err : err , query : query }
231+ } else {
232+ queryResults <- queryResult {issues : result .Issues , query : query }
233+ }
234+ }()
235+
236+ // Collect results from both queries
237+ var allIssues []* github.Issue
238+ seenURLs := make (map [string ]bool )
239+
240+ for range 2 {
241+ result := <- queryResults
242+ if result .err != nil {
243+ log .Printf ("[GITHUB] Query failed: %s - %v" , result .query , result .err )
244+ // Continue processing other query results even if one fails
245+ continue
246+ }
247+ log .Printf ("[GITHUB] Query completed: %s - found %d PRs" , result .query , len (result .issues ))
248+
249+ // Deduplicate PRs based on URL
250+ for _ , issue := range result .issues {
251+ url := issue .GetHTMLURL ()
252+ if ! seenURLs [url ] {
253+ seenURLs [url ] = true
254+ allIssues = append (allIssues , issue )
255+ }
256+ }
257+ }
258+ log .Printf ("[GITHUB] Both searches completed in %v, found %d unique PRs" , time .Since (searchStart ), len (allIssues ))
201259
202260 // Limit PRs for performance
203- if len (result . Issues ) > maxPRsToProcess {
204- log .Printf ("Limiting to %d PRs for performance (total: %d)" , maxPRsToProcess , len (result . Issues ))
205- result . Issues = result . Issues [:maxPRsToProcess ]
261+ if len (allIssues ) > maxPRsToProcess {
262+ log .Printf ("Limiting to %d PRs for performance (total: %d)" , maxPRsToProcess , len (allIssues ))
263+ allIssues = allIssues [:maxPRsToProcess ]
206264 }
207265
208266 // Process GitHub results immediately
209- for _ , issue := range result . Issues {
267+ for _ , issue := range allIssues {
210268 if ! issue .IsPullRequest () {
211269 continue
212270 }
@@ -229,26 +287,21 @@ func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incomin
229287 }
230288 }
231289
290+ // Only log summary, not individual PRs
232291 log .Printf ("[GITHUB] Found %d incoming, %d outgoing PRs from GitHub" , len (incoming ), len (outgoing ))
233- for i := range incoming {
234- log .Printf ("[GITHUB] Incoming PR: %s" , incoming [i ].URL )
235- }
236- for i := range outgoing {
237- log .Printf ("[GITHUB] Outgoing PR: %s" , outgoing [i ].URL )
238- }
239292
240293 // Fetch Turn API data
241294 if waitForTurn {
242295 // Synchronous - wait for Turn data
243- log . Println ( "[TURN] Fetching Turn API data synchronously before building menu..." )
244- app .fetchTurnDataSync (ctx , result . Issues , user , & incoming , & outgoing )
296+ // Fetch Turn API data synchronously before building menu
297+ app .fetchTurnDataSync (ctx , allIssues , user , & incoming , & outgoing )
245298 } else {
246299 // Asynchronous - start in background
247300 app .mu .Lock ()
248301 app .loadingTurnData = true
249302 app .pendingTurnResults = make ([]TurnResult , 0 ) // Reset buffer
250303 app .mu .Unlock ()
251- go app .fetchTurnDataAsync (ctx , result . Issues , user )
304+ go app .fetchTurnDataAsync (ctx , allIssues , user )
252305 }
253306
254307 return incoming , outgoing , nil
@@ -366,7 +419,10 @@ func (app *App) fetchTurnDataSync(ctx context.Context, issues []*github.Issue, u
366419 if action , exists := result .turnData .PRState .UnblockAction [user ]; exists {
367420 needsReview = true
368421 actionReason = action .Reason
369- log .Printf ("[TURN] UnblockAction for %s: Reason=%q, Kind=%q" , result .url , action .Reason , action .Kind )
422+ // Only log fresh API calls
423+ if ! result .wasFromCache {
424+ log .Printf ("[TURN] UnblockAction for %s: Reason=%q, Kind=%q" , result .url , action .Reason , action .Kind )
425+ }
370426 }
371427
372428 // Update the PR in the slices directly
@@ -400,7 +456,7 @@ func (app *App) fetchTurnDataSync(ctx context.Context, issues []*github.Issue, u
400456// fetchTurnDataAsync fetches Turn API data in the background and updates PRs as results arrive.
401457func (app * App ) fetchTurnDataAsync (ctx context.Context , issues []* github.Issue , user string ) {
402458 // Log start of Turn API queries
403- log . Print ( "[TURN] Starting Turn API queries in background" )
459+ // Start Turn API queries in background
404460
405461 turnStart := time .Now ()
406462 type prResult struct {
@@ -466,10 +522,10 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
466522 if action , exists := result .turnData .PRState .UnblockAction [user ]; exists {
467523 needsReview = true
468524 actionReason = action .Reason
469- log . Printf ( "[TURN] UnblockAction for %s: Reason=%q, Kind=%q" , result . url , action . Reason , action . Kind )
470- } else if ! result .wasFromCache {
471- // Only log "no action" for fresh API results, not cached ones
472- log . Printf ( "[TURN] No UnblockAction found for user %s on %s" , user , result . url )
525+ // Only log blocked PRs from fresh API calls
526+ if ! result .wasFromCache {
527+ log . Printf ( "[TURN] UnblockAction for %s: Reason=%q, Kind=%q" , result . url , action . Reason , action . Kind )
528+ }
473529 }
474530
475531 // Buffer the Turn result instead of applying immediately
@@ -486,13 +542,9 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
486542 app .mu .Unlock ()
487543
488544 updatesApplied ++
489- // Reduce verbosity - only log if not from cache or if blocked
490- if ! result .wasFromCache || needsReview {
491- cacheStatus := "fresh"
492- if result .wasFromCache {
493- cacheStatus = "cached"
494- }
495- log .Printf ("[TURN] %s data for %s (needsReview=%v, actionReason=%q)" , cacheStatus , result .url , needsReview , actionReason )
545+ // Only log fresh API calls (not cached)
546+ if ! result .wasFromCache {
547+ log .Printf ("[TURN] Fresh API data for %s (needsReview=%v)" , result .url , needsReview )
496548 }
497549 } else if result .err != nil {
498550 turnFailures ++
@@ -519,7 +571,10 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
519571 }
520572 }
521573
522- log .Printf ("[TURN] Applying %d buffered Turn results (%d from cache, %d fresh)" , len (pendingResults ), cacheHits , freshResults )
574+ // Only log if we have fresh results
575+ if freshResults > 0 {
576+ log .Printf ("[TURN] Applying %d buffered Turn results (%d from cache, %d fresh)" , len (pendingResults ), cacheHits , freshResults )
577+ }
523578
524579 // Track how many PRs actually changed
525580 var actualChanges int
@@ -530,15 +585,15 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
530585 }
531586 }
532587
588+ // Check for newly blocked PRs after Turn data is applied
589+ app .checkForNewlyBlockedPRs (ctx )
590+
533591 // Update tray title and menu with final Turn data if menu is already initialized
534592 app .setTrayTitle ()
535593 if app .menuInitialized {
536594 // Only trigger menu update if PR data actually changed
537595 if actualChanges > 0 {
538- log .Printf ("[TURN] Turn data applied - %d PRs actually changed, checking if menu needs update" , actualChanges )
539596 app .updateMenuIfChanged (ctx )
540- } else {
541- log .Print ("[TURN] Turn data applied - no PR changes detected (cached data unchanged), skipping menu update" )
542597 }
543598 }
544599}
0 commit comments