Skip to content

Commit 96fee33

Browse files
authored
Merge pull request #12 from ready-to-review/readme
finally fix the excessive menu rebuilds
2 parents 364addd + 4a1ff35 commit 96fee33

File tree

4 files changed

+135
-88
lines changed

4 files changed

+135
-88
lines changed

cache.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type cacheEntry struct {
2424
}
2525

2626
// turnData fetches Turn API data with caching.
27-
func (app *App) turnData(ctx context.Context, url string, updatedAt time.Time) (*turn.CheckResponse, error) {
27+
func (app *App) turnData(ctx context.Context, url string, updatedAt time.Time) (*turn.CheckResponse, bool, error) {
2828
// Create cache key from URL and updated timestamp
2929
key := fmt.Sprintf("%s-%s", url, updatedAt.Format(time.RFC3339))
3030
hash := sha256.Sum256([]byte(key))
@@ -43,7 +43,7 @@ func (app *App) turnData(ctx context.Context, url string, updatedAt time.Time) (
4343
}
4444
} else if time.Since(entry.CachedAt) < cacheTTL && entry.UpdatedAt.Equal(updatedAt) {
4545
// Check if cache is still valid (2 hour TTL)
46-
return entry.Data, nil
46+
return entry.Data, true, nil
4747
}
4848
}
4949
}
@@ -80,7 +80,7 @@ func (app *App) turnData(ctx context.Context, url string, updatedAt time.Time) (
8080
)
8181
if err != nil {
8282
log.Printf("Turn API error after %d retries (will use PR without metadata): %v", maxRetries, err)
83-
return nil, err
83+
return nil, false, err
8484
}
8585

8686
// Save to cache (don't fail if caching fails) - skip if --no-cache is set
@@ -102,7 +102,7 @@ func (app *App) turnData(ctx context.Context, url string, updatedAt time.Time) (
102102
}
103103
}
104104

105-
return data, nil
105+
return data, false, nil
106106
}
107107

108108
// cleanupOldCache removes cache files older than 5 days.

github.go

Lines changed: 94 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,11 @@ func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incomin
230230
}
231231

232232
log.Printf("[GITHUB] Found %d incoming, %d outgoing PRs from GitHub", len(incoming), len(outgoing))
233-
for _, pr := range incoming {
234-
log.Printf("[GITHUB] Incoming PR: %s", pr.URL)
233+
for i := range incoming {
234+
log.Printf("[GITHUB] Incoming PR: %s", incoming[i].URL)
235235
}
236-
for _, pr := range outgoing {
237-
log.Printf("[GITHUB] Outgoing PR: %s", pr.URL)
236+
for i := range outgoing {
237+
log.Printf("[GITHUB] Outgoing PR: %s", outgoing[i].URL)
238238
}
239239

240240
// Fetch Turn API data
@@ -255,41 +255,63 @@ func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incomin
255255
}
256256

257257
// updatePRData updates PR data with Turn API results.
258-
func (app *App) updatePRData(url string, needsReview bool, isOwner bool, actionReason string) *PR {
258+
func (app *App) updatePRData(url string, needsReview bool, isOwner bool, actionReason string) (*PR, bool) {
259259
app.mu.Lock()
260260
defer app.mu.Unlock()
261261

262262
if isOwner {
263263
// Update outgoing PRs
264264
for i := range app.outgoing {
265-
if app.outgoing[i].URL == url {
266-
app.outgoing[i].NeedsReview = needsReview
267-
app.outgoing[i].IsBlocked = needsReview
268-
app.outgoing[i].ActionReason = actionReason
269-
return &app.outgoing[i]
265+
if app.outgoing[i].URL != url {
266+
continue
270267
}
268+
// Check if Turn data was already applied for this UpdatedAt
269+
now := time.Now()
270+
if app.outgoing[i].TurnDataAppliedAt.After(app.outgoing[i].UpdatedAt) {
271+
// Turn data already applied for this PR version, no change
272+
return &app.outgoing[i], false
273+
}
274+
changed := app.outgoing[i].NeedsReview != needsReview ||
275+
app.outgoing[i].IsBlocked != needsReview ||
276+
app.outgoing[i].ActionReason != actionReason
277+
app.outgoing[i].NeedsReview = needsReview
278+
app.outgoing[i].IsBlocked = needsReview
279+
app.outgoing[i].ActionReason = actionReason
280+
app.outgoing[i].TurnDataAppliedAt = now
281+
return &app.outgoing[i], changed
271282
}
272283
} else {
273284
// Update incoming PRs
274285
for i := range app.incoming {
275-
if app.incoming[i].URL == url {
276-
app.incoming[i].NeedsReview = needsReview
277-
app.incoming[i].ActionReason = actionReason
278-
return &app.incoming[i]
286+
if app.incoming[i].URL != url {
287+
continue
288+
}
289+
// Check if Turn data was already applied for this UpdatedAt
290+
now := time.Now()
291+
if app.incoming[i].TurnDataAppliedAt.After(app.incoming[i].UpdatedAt) {
292+
// Turn data already applied for this PR version, no change
293+
return &app.incoming[i], false
279294
}
295+
changed := app.incoming[i].NeedsReview != needsReview ||
296+
app.incoming[i].ActionReason != actionReason
297+
app.incoming[i].NeedsReview = needsReview
298+
app.incoming[i].ActionReason = actionReason
299+
app.incoming[i].TurnDataAppliedAt = now
300+
return &app.incoming[i], changed
280301
}
281302
}
282-
return nil
303+
return nil, false
283304
}
284305

285306
// fetchTurnDataSync fetches Turn API data synchronously and updates PRs directly.
286307
func (app *App) fetchTurnDataSync(ctx context.Context, issues []*github.Issue, user string, incoming *[]PR, outgoing *[]PR) {
287308
turnStart := time.Now()
288309
type prResult struct {
289-
err error
290-
turnData *turn.CheckResponse
291-
url string
292-
isOwner bool
310+
err error
311+
turnData *turn.CheckResponse
312+
url string
313+
isOwner bool
314+
wasFromCache bool
293315
}
294316

295317
// Create a channel for results
@@ -312,13 +334,14 @@ func (app *App) fetchTurnDataSync(ctx context.Context, issues []*github.Issue, u
312334
updatedAt := issue.GetUpdatedAt().Time
313335

314336
// Call turnData - it now has proper exponential backoff with jitter
315-
turnData, err := app.turnData(ctx, url, updatedAt)
337+
turnData, wasFromCache, err := app.turnData(ctx, url, updatedAt)
316338

317339
results <- prResult{
318-
url: issue.GetHTMLURL(),
319-
turnData: turnData,
320-
err: err,
321-
isOwner: issue.GetUser().GetLogin() == user,
340+
url: issue.GetHTMLURL(),
341+
turnData: turnData,
342+
err: err,
343+
isOwner: issue.GetUser().GetLogin() == user,
344+
wasFromCache: wasFromCache,
322345
}
323346
}(issue)
324347
}
@@ -381,10 +404,11 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
381404

382405
turnStart := time.Now()
383406
type prResult struct {
384-
err error
385-
turnData *turn.CheckResponse
386-
url string
387-
isOwner bool
407+
err error
408+
turnData *turn.CheckResponse
409+
url string
410+
isOwner bool
411+
wasFromCache bool
388412
}
389413

390414
// Create a channel for results
@@ -407,13 +431,14 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
407431
updatedAt := issue.GetUpdatedAt().Time
408432

409433
// Call turnData - it now has proper exponential backoff with jitter
410-
turnData, err := app.turnData(ctx, url, updatedAt)
434+
turnData, wasFromCache, err := app.turnData(ctx, url, updatedAt)
411435

412436
results <- prResult{
413-
url: issue.GetHTMLURL(),
414-
turnData: turnData,
415-
err: err,
416-
isOwner: issue.GetUser().GetLogin() == user,
437+
url: issue.GetHTMLURL(),
438+
turnData: turnData,
439+
err: err,
440+
isOwner: issue.GetUser().GetLogin() == user,
441+
wasFromCache: wasFromCache,
417442
}
418443
}(issue)
419444
}
@@ -442,7 +467,8 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
442467
needsReview = true
443468
actionReason = action.Reason
444469
log.Printf("[TURN] UnblockAction for %s: Reason=%q, Kind=%q", result.url, action.Reason, action.Kind)
445-
} else {
470+
} else if !result.wasFromCache {
471+
// Only log "no action" for fresh API results, not cached ones
446472
log.Printf("[TURN] No UnblockAction found for user %s on %s", user, result.url)
447473
}
448474

@@ -452,14 +478,22 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
452478
NeedsReview: needsReview,
453479
IsOwner: result.isOwner,
454480
ActionReason: actionReason,
481+
WasFromCache: result.wasFromCache,
455482
}
456483

457484
app.mu.Lock()
458485
app.pendingTurnResults = append(app.pendingTurnResults, turnResult)
459486
app.mu.Unlock()
460487

461488
updatesApplied++
462-
log.Printf("[TURN] Turn data received for %s (needsReview=%v, actionReason=%q) - buffered", result.url, needsReview, actionReason)
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)
496+
}
463497
} else if result.err != nil {
464498
turnFailures++
465499
}
@@ -475,15 +509,36 @@ func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue,
475509
app.loadingTurnData = false
476510
app.mu.Unlock()
477511

478-
log.Printf("[TURN] Applying %d buffered Turn results", len(pendingResults))
512+
// Check if any results came from fresh API calls (not cache)
513+
var cacheHits, freshResults int
479514
for _, result := range pendingResults {
480-
app.updatePRData(result.URL, result.NeedsReview, result.IsOwner, result.ActionReason)
515+
if result.WasFromCache {
516+
cacheHits++
517+
} else {
518+
freshResults++
519+
}
520+
}
521+
522+
log.Printf("[TURN] Applying %d buffered Turn results (%d from cache, %d fresh)", len(pendingResults), cacheHits, freshResults)
523+
524+
// Track how many PRs actually changed
525+
var actualChanges int
526+
for _, result := range pendingResults {
527+
_, changed := app.updatePRData(result.URL, result.NeedsReview, result.IsOwner, result.ActionReason)
528+
if changed {
529+
actualChanges++
530+
}
481531
}
482532

483533
// Update tray title and menu with final Turn data if menu is already initialized
484534
app.setTrayTitle()
485535
if app.menuInitialized {
486-
log.Print("[TURN] Turn data loaded, checking if menu needs update")
487-
app.updateMenuIfChanged(ctx)
536+
// Only trigger menu update if PR data actually changed
537+
if actualChanges > 0 {
538+
log.Printf("[TURN] Turn data applied - %d PRs actually changed, checking if menu needs update", actualChanges)
539+
app.updateMenuIfChanged(ctx)
540+
} else {
541+
log.Print("[TURN] Turn data applied - no PR changes detected (cached data unchanged), skipping menu update")
542+
}
488543
}
489544
}

loginitem_darwin.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func validateAndEscapePathForAppleScript(path string) string {
3636

3737
// isLoginItem checks if the app is set to start at login.
3838
func isLoginItem(ctx context.Context) bool {
39-
appPath, err := getAppPath()
39+
appPath, err := appPath()
4040
if err != nil {
4141
log.Printf("Failed to get app path: %v", err)
4242
return false
@@ -67,7 +67,7 @@ func isLoginItem(ctx context.Context) bool {
6767

6868
// setLoginItem adds or removes the app from login items.
6969
func setLoginItem(ctx context.Context, enable bool) error {
70-
appPath, err := getAppPath()
70+
appPath, err := appPath()
7171
if err != nil {
7272
return fmt.Errorf("get app path: %w", err)
7373
}
@@ -131,8 +131,8 @@ func isRunningFromAppBundle() bool {
131131
return strings.Contains(execPath, ".app/Contents/MacOS/")
132132
}
133133

134-
// getAppPath returns the path to the application bundle.
135-
func getAppPath() (string, error) {
134+
// appPath returns the path to the application bundle.
135+
func appPath() (string, error) {
136136
// Get the executable path
137137
execPath, err := os.Executable()
138138
if err != nil {

0 commit comments

Comments
 (0)