@@ -8,8 +8,11 @@ class Session {
88 var fileWatcher : FileWatcher ?
99 /// Status color for the session tab. nil = default, green = done, etc.
1010 var statusColor : NSColor ?
11- /// Resolved GitHub PR info. nil = no PR found or not yet fetched.
12- var prInfo : GitHubPRInfo ?
11+ /// All GitHub PRs observed during this session's lifetime. The resolver only
12+ /// surfaces the PR for the currently-checked-out branch; this list accumulates
13+ /// every distinct PR (by number) seen across branch switches so users can
14+ /// still navigate to PRs opened earlier in the session.
15+ var prInfos : [ GitHubPRInfo ] = [ ]
1316 /// Short commit SHA of the worktree's current HEAD.
1417 var commitSha : String ?
1518 /// GitHub commit URL for the worktree's current HEAD, if the remote is GitHub.
@@ -64,44 +67,59 @@ class Session {
6467 }
6568
6669 func refreshPRStatus( ) {
67- let previousPR = prInfo
6870 GitHubPRResolver . resolve ( worktreePath: config. repoPath) { [ weak self] info in
6971 guard let self = self else { return }
70- self . prInfo = info
72+ guard let info = info else {
73+ // Resolver found nothing for the current branch — don't clear the
74+ // session's PR history; previously-opened PRs stay listed.
75+ self . onStatusChanged ? ( )
76+ return
77+ }
78+
79+ let existingIdx = self . prInfos. firstIndex { $0. number == info. number }
80+ let previousState = existingIdx. map { self . prInfos [ $0] . state }
81+
82+ if let idx = existingIdx {
83+ self . prInfos [ idx] = info
84+ } else {
85+ self . prInfos. append ( info)
86+ }
87+
88+ // Setup screen still shows a single per-session PR — keep writing the
89+ // latest detected one.
7190 SessionStore . shared. updatePRInfo (
7291 repoPath: self . config. repoPath,
7392 worktreeName: self . config. worktreeName,
7493 prInfo: info
7594 )
76- if let info = info {
77- let stateLabel : String
78- switch info. state {
79- case . open: stateLabel = " Open "
80- case . draft: stateLabel = " Draft "
81- case . merged: stateLabel = " Merged "
82- case . closed: stateLabel = " Closed "
83- }
8495
85- if previousPR == nil {
86- // Log activity when a PR is first detected
87- ActivityStore . shared. log (
88- . prOpened,
89- repoName: self . config. repoName,
90- detail: " \( info. number) " ,
91- prURL: info. url
92- )
93- }
96+ let stateLabel : String
97+ switch info. state {
98+ case . open: stateLabel = " Open "
99+ case . draft: stateLabel = " Draft "
100+ case . merged: stateLabel = " Merged "
101+ case . closed: stateLabel = " Closed "
102+ }
94103
95- // Update state if it changed (e.g., Open → Merged)
96- if previousPR? . state != info. state || previousPR == nil {
97- ActivityStore . shared. updatePRState (
98- repoName: self . config. repoName,
99- prNumber: " \( info. number) " ,
100- state: stateLabel,
101- url: info. url
102- )
103- }
104+ let isNew = existingIdx == nil
105+ if isNew {
106+ ActivityStore . shared. log (
107+ . prOpened,
108+ repoName: self . config. repoName,
109+ detail: " \( info. number) " ,
110+ prURL: info. url
111+ )
112+ }
113+
114+ if isNew || previousState != info. state {
115+ ActivityStore . shared. updatePRState (
116+ repoName: self . config. repoName,
117+ prNumber: " \( info. number) " ,
118+ state: stateLabel,
119+ url: info. url
120+ )
104121 }
122+
105123 self . onStatusChanged ? ( )
106124 }
107125 }
@@ -181,7 +199,7 @@ class SessionTabBar: NSView {
181199 @available ( * , unavailable)
182200 required init ? ( coder: NSCoder ) { fatalError ( ) }
183201
184- func update( sessions: [ ( repoName: String , sessionName: String , commitSha: String ? , commitURL: String ? , isActive: Bool , statusColor: NSColor ? , prInfo : GitHubPRInfo ? , diffStats: ( added: Int , deleted: Int ) ? ) ] ) {
202+ func update( sessions: [ ( repoName: String , sessionName: String , commitSha: String ? , commitURL: String ? , isActive: Bool , statusColor: NSColor ? , prInfos : [ GitHubPRInfo ] , diffStats: ( added: Int , deleted: Int ) ? ) ] ) {
185203 tabViews. forEach { $0. removeFromSuperview ( ) }
186204 tabViews. removeAll ( )
187205 detailViews. forEach { $0. removeFromSuperview ( ) }
@@ -266,7 +284,8 @@ class SessionTabBar: NSView {
266284 }
267285 }
268286
269- if let pr = active. prInfo {
287+ prURLsByButton. removeAll ( )
288+ for pr in active. prInfos {
270289 let prColor = SessionTab . color ( for: pr. state)
271290 let stateText = SessionTab . stateLabel ( for: pr. state)
272291
@@ -289,8 +308,13 @@ class SessionTabBar: NSView {
289308 prBtn. frame = NSRect ( x: detailX, y: centerY, width: prBtn. intrinsicContentSize. width, height: itemHeight)
290309 addSubview ( prBtn)
291310 detailViews. append ( prBtn)
292- prInfoURL = URL ( string: pr. url)
293- detailX += prBtn. frame. width + 12
311+ if let url = URL ( string: pr. url) {
312+ prURLsByButton [ prBtn] = url
313+ }
314+ detailX += prBtn. frame. width + 6
315+ }
316+ if !active. prInfos. isEmpty {
317+ detailX += 6
294318 }
295319
296320 // -- Diff stats (+N -M) --
@@ -322,14 +346,15 @@ class SessionTabBar: NSView {
322346 }
323347
324348 private var commitURL : URL ?
325- private var prInfoURL : URL ?
349+ private var prURLsByButton : [ NSButton : URL ] = [ : ]
326350
327351 @objc private func openCommitURL( _ sender: Any ? ) {
328352 if let url = commitURL { NSWorkspace . shared. open ( url) }
329353 }
330354
331355 @objc private func openPRURL( _ sender: Any ? ) {
332- if let url = prInfoURL { NSWorkspace . shared. open ( url) }
356+ guard let btn = sender as? NSButton , let url = prURLsByButton [ btn] else { return }
357+ NSWorkspace . shared. open ( url)
333358 }
334359
335360 @objc private func plusClicked( ) {
@@ -664,7 +689,7 @@ class SessionWindowController: NSWindowController {
664689
665690 private func refreshTabBar( ) {
666691 let tabs = sessions. enumerated ( ) . map { ( i, ws) in
667- ( repoName: ws. config. repoName, sessionName: ws. config. sessionName, commitSha: ws. commitSha, commitURL: ws. commitURL, isActive: i == activeIndex, statusColor: ws. statusColor, prInfo : ws. prInfo , diffStats: ws. diffStats)
692+ ( repoName: ws. config. repoName, sessionName: ws. config. sessionName, commitSha: ws. commitSha, commitURL: ws. commitURL, isActive: i == activeIndex, statusColor: ws. statusColor, prInfos : ws. prInfos , diffStats: ws. diffStats)
668693 }
669694 sessionTabBar. update ( sessions: tabs)
670695 }
@@ -712,6 +737,15 @@ class SessionWindowController: NSWindowController {
712737
713738 case " session.notify " :
714739 let colorName = command. command ?? " green "
740+
741+ // "work done" on the active session is just noise — the user is
742+ // already looking at it. Drop the green tint so the active tab
743+ // doesn't suddenly change color on completion.
744+ let isActive = activeIndex >= 0 && activeIndex < sessions. count && sessions [ activeIndex] === ws
745+ if isActive && colorName == " green " {
746+ return nil
747+ }
748+
715749 let color : NSColor
716750 switch colorName {
717751 case " green " : color = NSColor ( red: 0.0 , green: 0.5 , blue: 0.0 , alpha: 1.0 )
0 commit comments