@@ -278,6 +278,111 @@ func (app *App) addPRSection(ctx context.Context, prs []PR, sectionTitle string,
278278 }
279279}
280280
281+ // generateMenuTitles generates the list of menu item titles that would be shown
282+ // without actually building the UI. Used for change detection.
283+ func (app * App ) generateMenuTitles () []string {
284+ var titles []string
285+
286+ // Check for auth error first
287+ if app .authError != "" {
288+ titles = append (titles , "⚠️ Authentication Error" )
289+ titles = append (titles , app .authError )
290+ titles = append (titles , "To fix this issue:" )
291+ titles = append (titles , "1. Install GitHub CLI: brew install gh" )
292+ titles = append (titles , "2. Run: gh auth login" )
293+ titles = append (titles , "3. Or set GITHUB_TOKEN environment variable" )
294+ titles = append (titles , "Quit" )
295+ return titles
296+ }
297+
298+ app .mu .RLock ()
299+ incoming := make ([]PR , len (app .incoming ))
300+ copy (incoming , app .incoming )
301+ outgoing := make ([]PR , len (app .outgoing ))
302+ copy (outgoing , app .outgoing )
303+ hiddenOrgs := make (map [string ]bool )
304+ for org , hidden := range app .hiddenOrgs {
305+ hiddenOrgs [org ] = hidden
306+ }
307+ hideStale := app .hideStaleIncoming
308+ app .mu .RUnlock ()
309+
310+ // Add common menu items
311+ titles = append (titles , "Web Dashboard" )
312+
313+ // Generate PR section titles
314+ if len (incoming ) == 0 && len (outgoing ) == 0 {
315+ titles = append (titles , "No pull requests" )
316+ } else {
317+ // Add incoming PR titles
318+ if len (incoming ) > 0 {
319+ titles = append (titles , "📥 Incoming PRs" )
320+ titles = append (titles , app .generatePRSectionTitles (incoming , "Incoming" , hiddenOrgs , hideStale )... )
321+ }
322+
323+ // Add outgoing PR titles
324+ if len (outgoing ) > 0 {
325+ titles = append (titles , "📤 Outgoing PRs" )
326+ titles = append (titles , app .generatePRSectionTitles (outgoing , "Outgoing" , hiddenOrgs , hideStale )... )
327+ }
328+ }
329+
330+ // Add settings menu items
331+ titles = append (titles , "⚙️ Settings" )
332+ titles = append (titles , "Hide Stale Incoming PRs" )
333+ titles = append (titles , "Honks enabled" )
334+ titles = append (titles , "Auto-open in Browser" )
335+ titles = append (titles , "Hidden Organizations" )
336+ titles = append (titles , "Quit" )
337+
338+ return titles
339+ }
340+
341+ // generatePRSectionTitles generates the titles for a specific PR section
342+ func (app * App ) generatePRSectionTitles (prs []PR , sectionTitle string , hiddenOrgs map [string ]bool , hideStale bool ) []string {
343+ var titles []string
344+
345+ // Sort PRs by UpdatedAt (most recent first)
346+ sortedPRs := make ([]PR , len (prs ))
347+ copy (sortedPRs , prs )
348+ sort .Slice (sortedPRs , func (i , j int ) bool {
349+ return sortedPRs [i ].UpdatedAt .After (sortedPRs [j ].UpdatedAt )
350+ })
351+
352+ for prIndex := range sortedPRs {
353+ // Apply filters (same logic as in addPRSection)
354+ org := extractOrgFromRepo (sortedPRs [prIndex ].Repository )
355+ if org != "" && hiddenOrgs [org ] {
356+ continue
357+ }
358+
359+ if hideStale && sortedPRs [prIndex ].UpdatedAt .Before (time .Now ().Add (- stalePRThreshold )) {
360+ continue
361+ }
362+
363+ title := fmt .Sprintf ("%s #%d" , sortedPRs [prIndex ].Repository , sortedPRs [prIndex ].Number )
364+
365+ // Add bullet point or emoji for blocked PRs (same logic as in addPRSection)
366+ if sortedPRs [prIndex ].NeedsReview || sortedPRs [prIndex ].IsBlocked {
367+ prState , hasState := app .stateManager .GetPRState (sortedPRs [prIndex ].URL )
368+
369+ if hasState && ! prState .FirstBlockedAt .IsZero () && time .Since (prState .FirstBlockedAt ) < blockedPRIconDuration {
370+ if sectionTitle == "Outgoing" {
371+ title = fmt .Sprintf ("🎉 %s" , title )
372+ } else {
373+ title = fmt .Sprintf ("🪿 %s" , title )
374+ }
375+ } else {
376+ title = fmt .Sprintf ("• %s" , title )
377+ }
378+ }
379+
380+ titles = append (titles , title )
381+ }
382+
383+ return titles
384+ }
385+
281386// rebuildMenu completely rebuilds the menu from scratch.
282387func (app * App ) rebuildMenu (ctx context.Context ) {
283388 // Rebuild entire menu
@@ -478,7 +583,7 @@ func (app *App) addStaticMenuItems(ctx context.Context) {
478583
479584 // Audio cues
480585 // Add 'Audio cues' option
481- audioItem := systray .AddMenuItem ("Audio cues " , "Play sounds for notifications" )
586+ audioItem := systray .AddMenuItem ("Honks enabled " , "Play sounds for notifications" )
482587 app .mu .RLock ()
483588 if app .enableAudioCues {
484589 audioItem .Check ()
0 commit comments