From d2f814c27d0cca037658f29cd0c0ae54eab920df Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 24 Mar 2026 19:49:57 -0700 Subject: [PATCH] Simplify sidebar internals --- crates/sidebar/src/sidebar.rs | 332 ++++++++++++++++++++-------------- 1 file changed, 199 insertions(+), 133 deletions(-) diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index 53eebc4d2604e91caa4fc0fec57f27345becbae0..17268cf981f5aad009fb2ca1691c9081e88f006e 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -116,6 +116,18 @@ struct ThreadEntry { diff_stats: DiffStats, } +#[derive(Clone, Debug, PartialEq)] +enum ActiveEntry { + Thread(acp::SessionId), + Draft(Entity), +} + +impl ActiveEntry { + fn is_draft(&self) -> bool { + matches!(self, ActiveEntry::Draft(_)) + } +} + #[derive(Clone)] enum ListEntry { ProjectHeader { @@ -135,7 +147,6 @@ enum ListEntry { NewThread { path_list: PathList, workspace: Entity, - is_active_draft: bool, }, } @@ -237,17 +248,11 @@ pub struct Sidebar { filter_editor: Entity, list_state: ListState, contents: SidebarContents, - /// The index of the list item that currently has the keyboard focus - /// - /// Note: This is NOT the same as the active item. + /// The index of the list item that currently has the keyboard focus. selection: Option, - /// Derived from the active panel's thread in `rebuild_contents`. - /// Only updated when the panel returns `Some` — never cleared by - /// derivation, since the panel may transiently return `None` while - /// loading. User actions may write directly for immediate feedback. - focused_thread: Option, - agent_panel_visible: bool, - active_thread_is_draft: bool, + /// Tracks which entry is "active" — the thread or draft the user is + /// currently working with. + active_entry: Option, hovered_thread_index: Option, collapsed_groups: HashSet, expanded_groups: HashMap, @@ -280,6 +285,7 @@ impl Sidebar { window, |this, _multi_workspace, event: &MultiWorkspaceEvent, window, cx| match event { MultiWorkspaceEvent::ActiveWorkspaceChanged => { + this.active_entry = None; this.observe_draft_editor(cx); this.update_entries(cx); } @@ -337,9 +343,7 @@ impl Sidebar { list_state: ListState::new(0, gpui::ListAlignment::Top, px(1000.)), contents: SidebarContents::default(), selection: None, - focused_thread: None, - agent_panel_visible: false, - active_thread_is_draft: false, + active_entry: None, hovered_thread_index: None, collapsed_groups: HashSet::new(), expanded_groups: HashMap::new(), @@ -351,6 +355,12 @@ impl Sidebar { } } + fn is_agent_panel_visible(&self, cx: &App) -> bool { + self.multi_workspace.upgrade().map_or(false, |mw| { + AgentPanel::is_visible(mw.read(cx).workspace(), cx) + }) + } + fn is_active_workspace(&self, workspace: &Entity, cx: &App) -> bool { self.multi_workspace .upgrade() @@ -415,9 +425,6 @@ impl Sidebar { if let Some(agent_panel) = workspace.read(cx).panel::(cx) { self.subscribe_to_agent_panel(&agent_panel, window, cx); - if self.is_active_workspace(workspace, cx) { - self.agent_panel_visible = AgentPanel::is_visible(workspace, cx); - } self.observe_draft_editor(cx); } } @@ -438,7 +445,10 @@ impl Sidebar { .active_conversation_view() .is_some_and(|cv| cv.read(cx).parent_id(cx).is_none()); if is_new_draft { - this.focused_thread = None; + if let Some(multi_workspace) = this.multi_workspace.upgrade() { + let workspace = multi_workspace.read(cx).workspace().clone(); + this.active_entry = Some(ActiveEntry::Draft(workspace)); + } } this.observe_draft_editor(cx); this.update_entries(cx); @@ -465,14 +475,7 @@ impl Sidebar { let Some(workspace) = workspace.upgrade() else { return; }; - if !this.is_active_workspace(&workspace, cx) { - return; - } - - let is_visible = AgentPanel::is_visible(&workspace, cx); - - if this.agent_panel_visible != is_visible { - this.agent_panel_visible = is_visible; + if this.is_active_workspace(&workspace, cx) { cx.notify(); } }) @@ -614,23 +617,12 @@ impl Sidebar { let query = self.filter_editor.read(cx).text(cx); - // Re-derive agent_panel_visible from the active workspace so it stays - // correct after workspace switches. - self.agent_panel_visible = active_workspace - .as_ref() - .map_or(false, |ws| AgentPanel::is_visible(ws, cx)); - - // Derive active_thread_is_draft BEFORE focused_thread so we can - // use it as a guard below. - self.active_thread_is_draft = active_workspace - .as_ref() - .and_then(|ws| ws.read(cx).panel::(cx)) - .map_or(false, |panel| panel.read(cx).active_thread_is_draft(cx)); - - // Derive focused_thread from the active workspace's agent panel. - // Only update when the panel gives us a positive signal — if the - // panel returns None (e.g. still loading after a thread activation), - // keep the previous value so eager writes from user actions survive. + // Derive active_entry from the active workspace's agent panel. + // Thread when the panel has a non-draft thread, Draft when the + // workspace has no active thread (or no panel at all). + // Only overwrite with Thread on a positive signal — if the panel + // transiently returns None while loading, keep the previous value + // so eager writes from user actions survive. let panel_focused = active_workspace .as_ref() .and_then(|ws| ws.read(cx).panel::(cx)) @@ -640,8 +632,12 @@ impl Sidebar { .active_conversation_view() .and_then(|cv| cv.read(cx).parent_id(cx)) }); - if panel_focused.is_some() && !self.active_thread_is_draft { - self.focused_thread = panel_focused; + if let Some(session_id) = panel_focused { + self.active_entry = Some(ActiveEntry::Thread(session_id)); + } else if self.active_entry.is_none() { + if let Some(workspace) = active_workspace.as_ref() { + self.active_entry = Some(ActiveEntry::Draft(workspace.clone())); + } } let previous = mem::take(&mut self.contents); @@ -1033,9 +1029,8 @@ impl Sidebar { } } else { let thread_count = threads.len(); - let is_draft_for_workspace = self.agent_panel_visible - && self.active_thread_is_draft - && self.focused_thread.is_none() + let is_draft_for_workspace = self.is_agent_panel_visible(cx) + && self.active_entry.as_ref().is_some_and(|e| e.is_draft()) && is_active; let show_new_thread_entry = thread_count == 0 || is_draft_for_workspace; @@ -1059,7 +1054,6 @@ impl Sidebar { entries.push(ListEntry::NewThread { path_list: path_list.clone(), workspace: workspace.clone(), - is_active_draft: is_draft_for_workspace, }); } @@ -1084,10 +1078,9 @@ impl Sidebar { let is_promoted = thread.status == AgentThreadStatus::Running || thread.status == AgentThreadStatus::WaitingForConfirmation || notified_threads.contains(session_id) - || self - .focused_thread - .as_ref() - .is_some_and(|id| id == session_id); + || self.active_entry.as_ref().is_some_and(|entry| { + entry == &ActiveEntry::Thread(session_id.clone()) + }); if is_promoted { promoted_threads.insert(session_id.clone()); } @@ -1211,10 +1204,7 @@ impl Sidebar { ListEntry::NewThread { path_list, workspace, - is_active_draft, - } => { - self.render_new_thread(ix, path_list, workspace, *is_active_draft, is_selected, cx) - } + } => self.render_new_thread(ix, path_list, workspace, is_selected, cx), }; if is_group_header_after_first { @@ -1254,13 +1244,6 @@ impl Sidebar { IconName::ChevronDown }; - let has_new_thread_entry = self - .contents - .entries - .get(ix + 1) - .is_some_and(|entry| matches!(entry, ListEntry::NewThread { .. })); - let show_new_thread_button = !has_new_thread_entry && !self.has_filter_query(cx); - let workspace_for_remove = workspace.clone(); let workspace_for_menu = workspace.clone(); let workspace_for_open = workspace.clone(); @@ -1397,7 +1380,7 @@ impl Sidebar { .tooltip(Tooltip::text("Activate Workspace")) .on_click(cx.listener({ move |this, _, window, cx| { - this.focused_thread = None; + this.active_entry = None; if let Some(multi_workspace) = this.multi_workspace.upgrade() { multi_workspace.update(cx, |multi_workspace, cx| { multi_workspace @@ -1413,7 +1396,7 @@ impl Sidebar { })), ) }) - .when(show_new_thread_button, |this| { + .when(!self.has_filter_query(cx), |this| { this.child( IconButton::new( SharedString::from(format!( @@ -2062,10 +2045,10 @@ impl Sidebar { return; }; - // Set focused_thread eagerly so the sidebar highlight updates + // Set active_entry eagerly so the sidebar highlight updates // immediately, rather than waiting for a deferred AgentPanel // event which can race with ActiveWorkspaceChanged clearing it. - self.focused_thread = Some(session_info.session_id.clone()); + self.active_entry = Some(ActiveEntry::Thread(session_info.session_id.clone())); multi_workspace.update(cx, |multi_workspace, cx| { multi_workspace.activate(workspace.clone(), cx); @@ -2105,7 +2088,7 @@ impl Sidebar { .and_then(|sidebar| sidebar.downcast::().ok()) { target_sidebar.update(cx, |sidebar, cx| { - sidebar.focused_thread = Some(target_session_id); + sidebar.active_entry = Some(ActiveEntry::Thread(target_session_id)); sidebar.update_entries(cx); }); } @@ -2382,7 +2365,7 @@ impl Sidebar { // nearest thread within the same project group. We never cross group // boundaries — if the group has no other threads, clear focus and open // a blank new thread in the panel instead. - if self.focused_thread.as_ref() == Some(session_id) { + if self.active_entry.as_ref() == Some(&ActiveEntry::Thread(session_id.clone())) { let current_pos = self.contents.entries.iter().position(|entry| { matches!(entry, ListEntry::Thread(t) if &t.session_info.session_id == session_id) }); @@ -2436,7 +2419,8 @@ impl Sidebar { }); if let Some(next) = next_thread { - self.focused_thread = Some(next.session_info.session_id.clone()); + self.active_entry = + Some(ActiveEntry::Thread(next.session_info.session_id.clone())); // Use the thread's own workspace when it has one open (e.g. an absorbed // linked worktree thread that appears under the main workspace's header @@ -2464,7 +2448,7 @@ impl Sidebar { } } } else { - self.focused_thread = None; + self.active_entry = None; if let Some(workspace) = &group_workspace { if let Some(agent_panel) = workspace.read(cx).panel::(cx) { agent_panel.update(cx, |panel, cx| { @@ -2519,8 +2503,9 @@ impl Sidebar { let thread_workspace = thread.workspace.clone(); let is_hovered = self.hovered_thread_index == Some(ix); - let is_selected = self.agent_panel_visible - && self.focused_thread.as_ref() == Some(&session_info.session_id); + let is_selected = self.is_agent_panel_visible(cx) + && self.active_entry.as_ref() + == Some(&ActiveEntry::Thread(session_info.session_id.clone())); let is_running = matches!( thread.status, AgentThreadStatus::Running | AgentThreadStatus::WaitingForConfirmation @@ -2789,11 +2774,11 @@ impl Sidebar { return; }; - // Clear focused_thread immediately so no existing thread stays + // Set active_entry to Draft immediately so no existing thread stays // highlighted while the new blank thread is being shown. Without this, // if the target workspace is already active (so ActiveWorkspaceChanged // never fires), the previous thread's highlight would linger. - self.focused_thread = None; + self.active_entry = Some(ActiveEntry::Draft(workspace.clone())); multi_workspace.update(cx, |multi_workspace, cx| { multi_workspace.activate(workspace.clone(), cx); @@ -2814,11 +2799,13 @@ impl Sidebar { ix: usize, _path_list: &PathList, workspace: &Entity, - is_active_draft: bool, is_selected: bool, cx: &mut Context, ) -> AnyElement { - let is_active = is_active_draft && self.agent_panel_visible && self.active_thread_is_draft; + let is_active = self + .active_entry + .as_ref() + .is_some_and(|entry| entry == &ActiveEntry::Draft(workspace.clone())); let label: SharedString = if is_active { self.active_draft_text(cx) @@ -3613,6 +3600,86 @@ mod tests { ); } + #[gpui::test] + async fn test_activate_empty_workspace_marks_new_thread_active(cx: &mut TestAppContext) { + let project_a = init_test_project("/project-a", cx).await; + let (multi_workspace, cx) = cx + .add_window_view(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx)); + let sidebar = setup_sidebar(&multi_workspace, cx); + + let path_list_a = PathList::new(&[std::path::PathBuf::from("/project-a")]); + + // Workspace A has a thread. + save_thread_metadata( + acp::SessionId::new(Arc::from("thread-a1")), + "Thread A1".into(), + chrono::TimeZone::with_ymd_and_hms(&Utc, 2024, 1, 1, 0, 0, 0).unwrap(), + path_list_a.clone(), + cx, + ) + .await; + cx.run_until_parked(); + + // Add workspace B with no threads. + let fs = cx.update(|_, cx| ::global(cx)); + fs.as_fake() + .insert_tree("/project-b", serde_json::json!({ "src": {} })) + .await; + let project_b = project::Project::test(fs, ["/project-b".as_ref()], cx).await; + let _workspace_b = multi_workspace.update_in(cx, |mw, window, cx| { + mw.test_add_workspace(project_b.clone(), window, cx) + }); + cx.run_until_parked(); + + // After adding workspace B, it becomes active. Its new-thread entry + // should be marked active since it has no threads. + assert_eq!( + visible_entries_as_strings(&sidebar, cx), + vec![ + "v [project-a]", + " Thread A1", + "v [project-b]", + " [+ New Thread]", + ] + ); + + // Switch back to workspace A. + multi_workspace.update_in(cx, |mw, _window, cx| { + let workspace_a = mw.workspaces()[0].clone(); + mw.activate(workspace_a, cx); + }); + cx.run_until_parked(); + + // Workspace A is now active; workspace B's new-thread should no + // longer be marked active. + assert_eq!( + visible_entries_as_strings(&sidebar, cx), + vec![ + "v [project-a]", + " Thread A1", + "v [project-b]", + " [+ New Thread]", + ] + ); + + // Switch back to workspace B. + multi_workspace.update_in(cx, |mw, _window, cx| { + let workspace_b = mw.workspaces()[1].clone(); + mw.activate(workspace_b, cx); + }); + cx.run_until_parked(); + + // Workspace B is active again; its new-thread entry should be active. + assert_eq!( + visible_entries_as_strings(&sidebar, cx), + vec![ + "v [project-a]", + " Thread A1", + "v [project-b]", + " [+ New Thread]", + ] + ); + } #[gpui::test] async fn test_view_more_pagination(cx: &mut TestAppContext) { let project = init_test_project("/my-project", cx).await; @@ -5102,7 +5169,7 @@ mod tests { } #[gpui::test] - async fn test_focused_thread_tracks_user_intent(cx: &mut TestAppContext) { + async fn test_active_entry_tracks_user_intent(cx: &mut TestAppContext) { let project_a = init_test_project_with_agent_panel("/project-a", cx).await; let (multi_workspace, cx) = cx .add_window_view(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx)); @@ -5134,12 +5201,11 @@ mod tests { let workspace_a = multi_workspace.read_with(cx, |mw, _cx| mw.workspaces()[0].clone()); - // ── 1. Initial state: focused thread derived from active panel ───── + // ── 1. Initial state: workspace_b is active (no threads) so Draft ──── sidebar.read_with(cx, |sidebar, _cx| { - assert_eq!( - sidebar.focused_thread.as_ref(), - Some(&session_id_a), - "The active panel's thread should be focused on startup" + assert!( + sidebar.active_entry.as_ref().is_some_and(|e| e.is_draft()), + "Workspace B is active with no threads, so active_entry should be Draft" ); }); @@ -5163,8 +5229,8 @@ mod tests { sidebar.read_with(cx, |sidebar, _cx| { assert_eq!( - sidebar.focused_thread.as_ref(), - Some(&session_id_a), + sidebar.active_entry.as_ref(), + Some(&ActiveEntry::Thread(session_id_a.clone())), "After clicking a thread, it should be the focused thread" ); assert!( @@ -5218,8 +5284,8 @@ mod tests { sidebar.read_with(cx, |sidebar, _cx| { assert_eq!( - sidebar.focused_thread.as_ref(), - Some(&session_id_b), + sidebar.active_entry.as_ref(), + Some(&ActiveEntry::Thread(session_id_b.clone())), "Clicking a thread in another workspace should focus that thread" ); assert!( @@ -5235,9 +5301,9 @@ mod tests { sidebar.read_with(cx, |sidebar, _cx| { assert_eq!( - sidebar.focused_thread.as_ref(), - Some(&session_id_a), - "Switching workspace should seed focused_thread from the new active panel" + sidebar.active_entry.as_ref(), + Some(&ActiveEntry::Thread(session_id_a.clone())), + "Switching workspace should seed active_entry from the new active panel" ); assert!( has_thread_entry(sidebar, &session_id_a), @@ -5256,14 +5322,14 @@ mod tests { cx.run_until_parked(); // Panel B is not the active workspace's panel (workspace A is - // active), so opening a thread there should not change focused_thread. + // active), so opening a thread there should not change active_entry. // This prevents running threads in background workspaces from causing // the selection highlight to jump around. sidebar.read_with(cx, |sidebar, _cx| { assert_eq!( - sidebar.focused_thread.as_ref(), - Some(&session_id_a), - "Opening a thread in a non-active panel should not change focused_thread" + sidebar.active_entry.as_ref(), + Some(&ActiveEntry::Thread(session_id_a.clone())), + "Opening a thread in a non-active panel should not change active_entry" ); }); @@ -5274,14 +5340,14 @@ mod tests { sidebar.read_with(cx, |sidebar, _cx| { assert_eq!( - sidebar.focused_thread.as_ref(), - Some(&session_id_a), - "Defocusing the sidebar should not change focused_thread" + sidebar.active_entry.as_ref(), + Some(&ActiveEntry::Thread(session_id_a.clone())), + "Defocusing the sidebar should not change active_entry" ); }); // Switching workspaces via the multi_workspace (simulates clicking - // a workspace header) should clear focused_thread. + // a workspace header) should clear active_entry. multi_workspace.update_in(cx, |mw, window, cx| { if let Some(index) = mw.workspaces().iter().position(|w| w == &workspace_b) { mw.activate_index(index, window, cx); @@ -5291,9 +5357,9 @@ mod tests { sidebar.read_with(cx, |sidebar, _cx| { assert_eq!( - sidebar.focused_thread.as_ref(), - Some(&session_id_b2), - "Switching workspace should seed focused_thread from the new active panel" + sidebar.active_entry.as_ref(), + Some(&ActiveEntry::Thread(session_id_b2.clone())), + "Switching workspace should seed active_entry from the new active panel" ); assert!( has_thread_entry(sidebar, &session_id_b2), @@ -5301,10 +5367,10 @@ mod tests { ); }); - // ── 8. Focusing the agent panel thread keeps focused_thread ──── + // ── 8. Focusing the agent panel thread keeps active_entry ──── // Workspace B still has session_id_b2 loaded in the agent panel. // Clicking into the thread (simulated by focusing its view) should - // keep focused_thread since it was already seeded on workspace switch. + // keep active_entry since it was already seeded on workspace switch. panel_b.update_in(cx, |panel, window, cx| { if let Some(thread_view) = panel.active_conversation_view() { thread_view.read(cx).focus_handle(cx).focus(window, cx); @@ -5314,9 +5380,9 @@ mod tests { sidebar.read_with(cx, |sidebar, _cx| { assert_eq!( - sidebar.focused_thread.as_ref(), - Some(&session_id_b2), - "Focusing the agent panel thread should set focused_thread" + sidebar.active_entry.as_ref(), + Some(&ActiveEntry::Thread(session_id_b2.clone())), + "Focusing the agent panel thread should set active_entry" ); assert!( has_thread_entry(sidebar, &session_id_b2), @@ -5356,8 +5422,8 @@ mod tests { // because the panel has a thread with messages. sidebar.read_with(cx, |sidebar, _cx| { assert!( - !sidebar.active_thread_is_draft, - "Panel has a thread with messages, so it should not be a draft" + !sidebar.active_entry.as_ref().is_some_and(|e| e.is_draft()), + "Panel has a thread with messages, so active_entry should not be a draft" ); }); @@ -5384,13 +5450,13 @@ mod tests { ); // The "New Thread" button must still be clickable (not stuck in - // "active/draft" state). Verify that `active_thread_is_draft` is - // false — the panel still has the old thread with messages. + // "active/draft" state). Verify that `active_entry` is not a + // draft — the panel still has the old thread with messages. sidebar.read_with(cx, |sidebar, _cx| { assert!( - !sidebar.active_thread_is_draft, + !sidebar.active_entry.as_ref().is_some_and(|e| e.is_draft()), "After adding a folder the panel still has a thread with messages, \ - so active_thread_is_draft should be false" + so active_entry should not be a draft" ); }); @@ -5406,7 +5472,7 @@ mod tests { // state (no messages on the new thread). sidebar.read_with(cx, |sidebar, _cx| { assert!( - sidebar.active_thread_is_draft, + sidebar.active_entry.as_ref().is_some_and(|e| e.is_draft()), "After creating a new thread the panel should be in draft state" ); }); @@ -5460,11 +5526,11 @@ mod tests { sidebar.read_with(cx, |sidebar, _cx| { assert!( - sidebar.focused_thread.is_none(), - "focused_thread should be cleared after Cmd-N" + sidebar.active_entry.as_ref().is_some_and(|e| e.is_draft()), + "active_entry should be a Draft after Cmd-N" ); assert!( - sidebar.active_thread_is_draft, + sidebar.active_entry.as_ref().is_some_and(|e| e.is_draft()), "the new blank thread should be a draft" ); }); @@ -5592,11 +5658,11 @@ mod tests { sidebar.read_with(cx, |sidebar, _cx| { assert!( - sidebar.focused_thread.is_none(), - "focused_thread should be cleared after Cmd-N" + sidebar.active_entry.as_ref().is_some_and(|e| e.is_draft()), + "active_entry should be a Draft after Cmd-N" ); assert!( - sidebar.active_thread_is_draft, + sidebar.active_entry.as_ref().is_some_and(|e| e.is_draft()), "the new blank thread should be a draft" ); }); @@ -6749,9 +6815,9 @@ mod tests { "should activate the window that already owns the matching workspace" ); sidebar.read_with(cx_a, |sidebar, _| { - assert_eq!( - sidebar.focused_thread, None, - "source window's sidebar should not eagerly claim focus for a thread opened in another window" + assert!( + sidebar.active_entry.as_ref().is_some_and(|e| e.is_draft()), + "source window's sidebar should show Draft for its own workspace, not claim a thread from another window" ); }); } @@ -6825,15 +6891,15 @@ mod tests { "should activate the window that already owns the matching workspace" ); sidebar_a.read_with(cx_a, |sidebar, _| { - assert_eq!( - sidebar.focused_thread, None, - "source window's sidebar should not eagerly claim focus for a thread opened in another window" + assert!( + sidebar.active_entry.as_ref().is_some_and(|e| e.is_draft()), + "source window's sidebar should show Draft for its own workspace, not claim a thread from another window" ); }); sidebar_b.read_with(cx_b, |sidebar, _| { assert_eq!( - sidebar.focused_thread.as_ref(), - Some(&session_id), + sidebar.active_entry.as_ref(), + Some(&ActiveEntry::Thread(session_id.clone())), "target window's sidebar should eagerly focus the activated archived thread" ); }); @@ -6887,8 +6953,8 @@ mod tests { ); sidebar_a.read_with(cx_a, |sidebar, _| { assert_eq!( - sidebar.focused_thread.as_ref(), - Some(&session_id), + sidebar.active_entry.as_ref(), + Some(&ActiveEntry::Thread(session_id.clone())), "current window's sidebar should eagerly focus the activated archived thread" ); }); @@ -7044,11 +7110,11 @@ mod tests { // The sidebar should track T2 as the focused thread (derived from the // main panel's active view). - let focused = sidebar.read_with(cx, |s, _| s.focused_thread.clone()); + let focused = sidebar.read_with(cx, |s, _| s.active_entry.clone()); assert_eq!( focused, - Some(thread2_session_id.clone()), - "focused thread should be Thread 2 before archiving: {:?}", + Some(ActiveEntry::Thread(thread2_session_id.clone())), + "focused entry should be Thread 2 before archiving: {:?}", focused );