@@ -126,22 +126,26 @@ pub struct App {
126126 remote_log_receiver : Receiver < ( String , String ) > ,
127127 /// Remote session IDs whose logs are currently loading in the background.
128128 remote_logs_loading : HashSet < String > ,
129+ /// Sends completed session loads from background workers to the main loop.
130+ session_load_sender : Sender < ( u64 , Vec < CopilotSession > ) > ,
131+ /// Receives completed session loads without blocking startup or rendering.
132+ session_load_receiver : Receiver < ( u64 , Vec < CopilotSession > ) > ,
133+ /// Whether a session list load is currently in flight.
134+ sessions_loading : bool ,
135+ /// Monotonically increasing token used to ignore stale background loads.
136+ session_load_generation : u64 ,
129137}
130138
131139impl App {
132140 pub fn new ( copilot_dir : PathBuf , launch_dir : PathBuf ) -> Self {
133- let sessions = load_sessions ( & copilot_dir) ;
134- let flat_list = build_flat_list ( & sessions, SessionFilter :: All , "" ) ;
135-
136- let selected_session = flat_list. first ( ) . copied ( ) ;
137- let cursor = 0 ;
138141 let ( remote_log_sender, remote_log_receiver) = mpsc:: channel ( ) ;
142+ let ( session_load_sender, session_load_receiver) = mpsc:: channel ( ) ;
139143
140144 App {
141- sessions,
142- flat_list,
143- cursor,
144- selected_session,
145+ sessions : Vec :: new ( ) ,
146+ flat_list : Vec :: new ( ) ,
147+ cursor : 0 ,
148+ selected_session : None ,
145149 active_panel : Panel :: Sessions ,
146150 copilot_dir,
147151 launch_dir,
@@ -164,11 +168,36 @@ impl App {
164168 remote_log_sender,
165169 remote_log_receiver,
166170 remote_logs_loading : HashSet :: new ( ) ,
171+ session_load_sender,
172+ session_load_receiver,
173+ sessions_loading : false ,
174+ session_load_generation : 0 ,
167175 }
168176 }
169177
170178 pub fn reload ( & mut self ) {
171- self . replace_sessions ( load_sessions ( & self . copilot_dir ) ) ;
179+ self . session_load_generation = self . session_load_generation . wrapping_add ( 1 ) ;
180+ self . sessions_loading = true ;
181+ let generation = self . session_load_generation ;
182+ let copilot_dir = self . copilot_dir . clone ( ) ;
183+ let sender = self . session_load_sender . clone ( ) ;
184+ std:: thread:: spawn ( move || {
185+ let sessions = load_sessions ( & copilot_dir) ;
186+ let _ = sender. send ( ( generation, sessions) ) ;
187+ } ) ;
188+ }
189+
190+ pub fn poll_session_loads ( & mut self ) {
191+ while let Ok ( ( generation, sessions) ) = self . session_load_receiver . try_recv ( ) {
192+ if generation == self . session_load_generation {
193+ self . sessions_loading = false ;
194+ self . replace_sessions ( sessions) ;
195+ }
196+ }
197+ }
198+
199+ pub fn is_loading_sessions ( & self ) -> bool {
200+ self . sessions_loading
172201 }
173202
174203 pub fn refresh_statuses ( & mut self ) -> bool {
@@ -263,8 +292,10 @@ impl App {
263292 . find ( |session| !baseline. contains ( & session. id ) )
264293 . map ( |session| session. id . clone ( ) ) ?;
265294
295+ self . session_load_generation = self . session_load_generation . wrapping_add ( 1 ) ;
266296 self . new_session_reload_baseline = None ;
267297 self . replace_sessions ( sessions) ;
298+ self . sessions_loading = false ;
268299 Some ( new_session_id)
269300 }
270301
@@ -493,7 +524,7 @@ impl App {
493524 let sender = self . remote_log_sender . clone ( ) ;
494525 std:: thread:: spawn ( move || {
495526 let log = load_remote_task_log ( & id) ;
496- drop ( sender. send ( ( id, log) ) ) ;
527+ let _ = sender. send ( ( id, log) ) ;
497528 } ) ;
498529 }
499530
@@ -769,6 +800,7 @@ mod tests {
769800
770801 fn app_with_sessions ( sessions : Vec < CopilotSession > ) -> App {
771802 let ( remote_log_sender, remote_log_receiver) = mpsc:: channel ( ) ;
803+ let ( session_load_sender, session_load_receiver) = mpsc:: channel ( ) ;
772804 let flat_list = build_flat_list ( & sessions, SessionFilter :: All , "" ) ;
773805 let selected_session = flat_list. first ( ) . copied ( ) ;
774806 App {
@@ -798,6 +830,10 @@ mod tests {
798830 remote_log_sender,
799831 remote_log_receiver,
800832 remote_logs_loading : HashSet :: new ( ) ,
833+ session_load_sender,
834+ session_load_receiver,
835+ sessions_loading : false ,
836+ session_load_generation : 0 ,
801837 }
802838 }
803839
@@ -1060,6 +1096,43 @@ mod tests {
10601096 assert_eq ! ( app. directory_suggestion_cursor( ) , Some ( 1 ) ) ;
10611097 }
10621098
1099+ #[ test]
1100+ fn app_new_starts_with_empty_non_loading_session_list ( ) {
1101+ let app = App :: new ( PathBuf :: from ( "/tmp/copilot" ) , PathBuf :: from ( "/tmp" ) ) ;
1102+
1103+ assert ! ( app. sessions. is_empty( ) ) ;
1104+ assert ! ( app. flat_list. is_empty( ) ) ;
1105+ assert_eq ! ( app. selected_session, None ) ;
1106+ assert ! ( !app. is_loading_sessions( ) ) ;
1107+ }
1108+
1109+ #[ test]
1110+ fn reload_starts_background_session_load ( ) {
1111+ let mut app = App :: new ( PathBuf :: from ( "/tmp/copilot" ) , PathBuf :: from ( "/tmp" ) ) ;
1112+
1113+ app. reload ( ) ;
1114+
1115+ assert ! ( app. is_loading_sessions( ) ) ;
1116+ }
1117+
1118+ #[ test]
1119+ fn poll_session_loads_replaces_sessions ( ) {
1120+ let mut app = App :: new ( PathBuf :: from ( "/tmp/copilot" ) , PathBuf :: from ( "/tmp" ) ) ;
1121+ app. sessions_loading = true ;
1122+ app. session_load_sender
1123+ . send ( (
1124+ app. session_load_generation ,
1125+ vec ! [ session( "local" , SessionSource :: Local ) ] ,
1126+ ) )
1127+ . unwrap ( ) ;
1128+
1129+ app. poll_session_loads ( ) ;
1130+
1131+ assert ! ( !app. is_loading_sessions( ) ) ;
1132+ assert_eq ! ( app. sessions. len( ) , 1 ) ;
1133+ assert_eq ! ( app. selected_session, Some ( 0 ) ) ;
1134+ }
1135+
10631136 #[ test]
10641137 fn moving_cursor_to_remote_task_does_not_load_log ( ) {
10651138 let mut app = app_with_sessions ( vec ! [
0 commit comments