Revert "sidebar: Refactor thread time storage (#53982)" (#54078)

Cameron Mcloughlin created

Change summary

crates/agent_ui/src/thread_import.rs         | 19 +---
crates/agent_ui/src/thread_metadata_store.rs | 70 +-----------------
crates/sidebar/src/sidebar.rs                | 83 +++++++++++++++------
crates/sidebar/src/sidebar_tests.rs          | 57 +++++++-------
4 files changed, 98 insertions(+), 131 deletions(-)

Detailed changes

crates/agent_ui/src/thread_import.rs 🔗

@@ -565,17 +565,15 @@ fn collect_importable_threads(
             let Some(folder_paths) = session.work_dirs else {
                 continue;
             };
-            let updated_at = session.updated_at.unwrap_or_else(|| Utc::now());
             to_insert.push(ThreadMetadata {
                 thread_id: ThreadId::new(),
                 session_id: Some(session.session_id),
                 agent_id: agent_id.clone(),
                 title: session.title,
-                updated_at,
+                updated_at: session.updated_at.unwrap_or_else(|| Utc::now()),
                 created_at: session.created_at,
                 worktree_paths: WorktreePaths::from_folder_paths(&folder_paths),
                 remote_connection: remote_connection.clone(),
-                last_user_interaction: updated_at,
                 archived: true,
             });
         }
@@ -910,19 +908,12 @@ mod tests {
         let thread_id = uuid::Uuid::new_v4();
         let session_id = uuid::Uuid::new_v4().to_string();
         connection
-            .exec_bound::<(uuid::Uuid, &str, &str, &str, bool, &str)>(
+            .exec_bound::<(uuid::Uuid, &str, &str, &str, bool)>(
                 "INSERT INTO sidebar_threads \
-                 (thread_id, session_id, title, updated_at, archived, last_user_interaction) \
-                 VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
+                 (thread_id, session_id, title, updated_at, archived) \
+                 VALUES (?1, ?2, ?3, ?4, ?5)",
             )
-            .unwrap()((
-            thread_id,
-            session_id.as_str(),
-            title,
-            updated_at,
-            archived,
-            updated_at,
-        ))
+            .unwrap()((thread_id, session_id.as_str(), title, updated_at, archived))
         .unwrap();
     }
 

crates/agent_ui/src/thread_metadata_store.rs 🔗

@@ -127,7 +127,6 @@ fn migrate_thread_metadata(cx: &mut App) -> Task<anyhow::Result<()>> {
                         created_at: entry.created_at,
                         worktree_paths: WorktreePaths::from_folder_paths(&entry.folder_paths),
                         remote_connection: None,
-                        last_user_interaction: entry.updated_at,
                         archived: true,
                     })
                 })
@@ -297,7 +296,6 @@ pub struct ThreadMetadata {
     pub created_at: Option<DateTime<Utc>>,
     pub worktree_paths: WorktreePaths,
     pub remote_connection: Option<RemoteConnectionOptions>,
-    pub last_user_interaction: DateTime<Utc>,
     pub archived: bool,
 }
 
@@ -674,20 +672,6 @@ impl ThreadMetadataStore {
             .log_err();
     }
 
-    pub fn update_last_user_interaction(
-        &mut self,
-        thread_id: ThreadId,
-        time: DateTime<Utc>,
-        cx: &mut Context<Self>,
-    ) {
-        if let Some(thread) = self.threads.get(&thread_id).cloned() {
-            self.save_internal(ThreadMetadata {
-                last_user_interaction: time,
-                ..thread
-            });
-            cx.notify();
-        }
-    }
     fn cache_thread_metadata(&mut self, metadata: ThreadMetadata) {
         let Some(session_id) = metadata.session_id.as_ref() else {
             debug_panic!("cannot store thread metadata without a session_id");
@@ -1206,7 +1190,6 @@ impl ThreadMetadataStore {
             updated_at,
             worktree_paths,
             remote_connection,
-            last_user_interaction: updated_at,
             archived,
         };
 
@@ -1294,35 +1277,6 @@ impl Domain for ThreadMetadataDb {
             DROP TABLE sidebar_threads;
             ALTER TABLE sidebar_threads_v2 RENAME TO sidebar_threads;
         ),
-        sql!(
-            ALTER TABLE sidebar_threads ADD COLUMN last_user_interaction TEXT;
-
-            UPDATE sidebar_threads SET last_user_interaction = updated_at
-            WHERE last_user_interaction IS NULL;
-
-            CREATE TABLE sidebar_threads_v3(
-                thread_id BLOB PRIMARY KEY,
-                session_id TEXT,
-                agent_id TEXT,
-                title TEXT NOT NULL,
-                updated_at TEXT NOT NULL,
-                created_at TEXT,
-                folder_paths TEXT,
-                folder_paths_order TEXT,
-                archived INTEGER DEFAULT 0,
-                main_worktree_paths TEXT,
-                main_worktree_paths_order TEXT,
-                remote_connection TEXT,
-                last_user_interaction TEXT NOT NULL
-            ) STRICT;
-
-            INSERT INTO sidebar_threads_v3(thread_id, session_id, agent_id, title, updated_at, created_at, folder_paths, folder_paths_order, archived, main_worktree_paths, main_worktree_paths_order, remote_connection, last_user_interaction)
-            SELECT thread_id, session_id, agent_id, title, updated_at, created_at, folder_paths, folder_paths_order, archived, main_worktree_paths, main_worktree_paths_order, remote_connection, last_user_interaction
-            FROM sidebar_threads;
-
-            DROP TABLE sidebar_threads;
-            ALTER TABLE sidebar_threads_v3 RENAME TO sidebar_threads;
-        ),
         sql!(
             DELETE FROM thread_archived_worktrees
             WHERE thread_id IN (
@@ -1353,7 +1307,7 @@ impl ThreadMetadataDb {
 
     const LIST_QUERY: &str = "SELECT thread_id, session_id, agent_id, title, updated_at, \
         created_at, folder_paths, folder_paths_order, archived, main_worktree_paths, \
-        main_worktree_paths_order, remote_connection, last_user_interaction \
+        main_worktree_paths_order, remote_connection \
         FROM sidebar_threads \
         WHERE session_id IS NOT NULL \
         ORDER BY updated_at DESC";
@@ -1406,11 +1360,10 @@ impl ThreadMetadataDb {
             .context("serialize thread metadata remote connection")?;
         let thread_id = row.thread_id;
         let archived = row.archived;
-        let last_user_interaction = row.last_user_interaction.to_rfc3339();
 
         self.write(move |conn| {
-            let sql = "INSERT INTO sidebar_threads(thread_id, session_id, agent_id, title, updated_at, created_at, folder_paths, folder_paths_order, archived, main_worktree_paths, main_worktree_paths_order, remote_connection, last_user_interaction) \
-                       VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13) \
+            let sql = "INSERT INTO sidebar_threads(thread_id, session_id, agent_id, title, updated_at, created_at, folder_paths, folder_paths_order, archived, main_worktree_paths, main_worktree_paths_order, remote_connection) \
+                       VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12) \
                        ON CONFLICT(thread_id) DO UPDATE SET \
                            session_id = excluded.session_id, \
                            agent_id = excluded.agent_id, \
@@ -1422,8 +1375,7 @@ impl ThreadMetadataDb {
                            archived = excluded.archived, \
                            main_worktree_paths = excluded.main_worktree_paths, \
                            main_worktree_paths_order = excluded.main_worktree_paths_order, \
-                           remote_connection = excluded.remote_connection, \
-                           last_user_interaction = excluded.last_user_interaction";
+                           remote_connection = excluded.remote_connection";
             let mut stmt = Statement::prepare(conn, sql)?;
             let mut i = stmt.bind(&thread_id, 1)?;
             i = stmt.bind(&session_id, i)?;
@@ -1436,8 +1388,7 @@ impl ThreadMetadataDb {
             i = stmt.bind(&archived, i)?;
             i = stmt.bind(&main_worktree_paths, i)?;
             i = stmt.bind(&main_worktree_paths_order, i)?;
-            i = stmt.bind(&remote_connection, i)?;
-            stmt.bind(&last_user_interaction, i)?;
+            stmt.bind(&remote_connection, i)?;
             stmt.exec()
         })
         .await
@@ -1593,7 +1544,6 @@ impl Column for ThreadMetadata {
             Column::column(statement, next)?;
         let (remote_connection_json, next): (Option<String>, i32) =
             Column::column(statement, next)?;
-        let (last_user_interaction_str, next): (String, i32) = Column::column(statement, next)?;
 
         let agent_id = agent_id
             .map(|id| AgentId::new(id))
@@ -1630,9 +1580,6 @@ impl Column for ThreadMetadata {
             .transpose()
             .context("deserialize thread metadata remote connection")?;
 
-        let last_user_interaction =
-            DateTime::parse_from_rfc3339(&last_user_interaction_str)?.with_timezone(&Utc);
-
         let worktree_paths = WorktreePaths::from_path_lists(main_worktree_paths, folder_paths)
             .unwrap_or_else(|_| WorktreePaths::default());
 
@@ -1652,7 +1599,6 @@ impl Column for ThreadMetadata {
                 created_at,
                 worktree_paths,
                 remote_connection,
-                last_user_interaction,
                 archived,
             },
             next,
@@ -1742,7 +1688,6 @@ mod tests {
             created_at: Some(updated_at),
             worktree_paths: WorktreePaths::from_folder_paths(&folder_paths),
             remote_connection: None,
-            last_user_interaction: updated_at,
         }
     }
 
@@ -1924,7 +1869,6 @@ mod tests {
             created_at: Some(updated_time),
             worktree_paths: WorktreePaths::from_folder_paths(&second_paths),
             remote_connection: None,
-            last_user_interaction: updated_time,
             archived: false,
         };
 
@@ -2008,7 +1952,6 @@ mod tests {
             created_at: Some(now - chrono::Duration::seconds(10)),
             worktree_paths: WorktreePaths::from_folder_paths(&project_a_paths),
             remote_connection: None,
-            last_user_interaction: now - chrono::Duration::seconds(10),
             archived: false,
         };
 
@@ -2129,7 +2072,6 @@ mod tests {
             created_at: Some(existing_updated_at),
             worktree_paths: WorktreePaths::from_folder_paths(&project_paths),
             remote_connection: None,
-            last_user_interaction: existing_updated_at,
             archived: false,
         };
 
@@ -2803,7 +2745,6 @@ mod tests {
             created_at: Some(now),
             worktree_paths: linked_worktree_paths.clone(),
             remote_connection: None,
-            last_user_interaction: now,
         };
 
         let remote_linked_thread = ThreadMetadata {
@@ -2816,7 +2757,6 @@ mod tests {
             created_at: Some(now - chrono::Duration::seconds(1)),
             worktree_paths: linked_worktree_paths,
             remote_connection: Some(remote_a.clone()),
-            last_user_interaction: now - chrono::Duration::seconds(1),
         };
 
         cx.update(|cx| {

crates/sidebar/src/sidebar.rs 🔗

@@ -354,9 +354,15 @@ pub struct Sidebar {
     /// Tracks which sidebar entry is currently active (highlighted).
     active_entry: Option<ActiveEntry>,
     hovered_thread_index: Option<usize>,
-    /// The last time the user opened/focused each thread in the agent panel.
-    /// Used for ordering the ctrl-tab switcher.
-    last_accessed: HashMap<ThreadId, DateTime<Utc>>,
+
+    /// Updated only in response to explicit user actions (clicking a
+    /// thread, confirming in the thread switcher, etc.) — never from
+    /// background data changes. Used to sort the thread switcher popup.
+    thread_last_accessed: HashMap<acp::SessionId, DateTime<Utc>>,
+    /// Updated when the user presses a key to send or queue a message.
+    /// Used for sorting threads in the sidebar and as a secondary sort
+    /// key in the thread switcher.
+    thread_last_message_sent_or_queued: HashMap<agent_ui::ThreadId, DateTime<Utc>>,
     thread_switcher: Option<Entity<ThreadSwitcher>>,
     _thread_switcher_subscriptions: Vec<gpui::Subscription>,
     pending_thread_activation: Option<agent_ui::ThreadId>,
@@ -450,7 +456,8 @@ impl Sidebar {
             active_entry: None,
             hovered_thread_index: None,
 
-            last_accessed: HashMap::default(),
+            thread_last_accessed: HashMap::new(),
+            thread_last_message_sent_or_queued: HashMap::new(),
             thread_switcher: None,
             _thread_switcher_subscriptions: Vec::new(),
             pending_thread_activation: None,
@@ -631,10 +638,7 @@ impl Sidebar {
                     this.update_entries(cx);
                 }
                 AgentPanelEvent::MessageSentOrQueued { thread_id } => {
-                    let now = Utc::now();
-                    ThreadMetadataStore::global(cx).update(cx, |store, cx| {
-                        store.update_last_user_interaction(*thread_id, now, cx);
-                    });
+                    this.record_thread_message_sent(thread_id);
                     this.update_entries(cx);
                 }
             },
@@ -884,6 +888,7 @@ impl Sidebar {
 
         let mut entries = Vec::new();
         let mut notified_threads = previous.notified_threads;
+        let mut current_session_ids: HashSet<acp::SessionId> = HashSet::new();
         let mut current_thread_ids: HashSet<agent_ui::ThreadId> = HashSet::new();
         let mut project_header_indices: Vec<usize> = Vec::new();
         let mut seen_thread_ids: HashSet<agent_ui::ThreadId> = HashSet::new();
@@ -1125,9 +1130,9 @@ impl Sidebar {
                 }
 
                 threads.sort_by(|a, b| {
-                    b.metadata
-                        .last_user_interaction
-                        .cmp(&a.metadata.last_user_interaction)
+                    let a_time = self.display_time(&a.metadata);
+                    let b_time = self.display_time(&b.metadata);
+                    b_time.cmp(&a_time)
                 });
             } else {
                 for info in live_infos {
@@ -1200,6 +1205,9 @@ impl Sidebar {
                 });
 
                 for thread in matched_threads {
+                    if let Some(sid) = thread.metadata.session_id.clone() {
+                        current_session_ids.insert(sid);
+                    }
                     current_thread_ids.insert(thread.metadata.thread_id);
                     entries.push(thread.into());
                 }
@@ -1220,6 +1228,9 @@ impl Sidebar {
                 }
 
                 for thread in threads {
+                    if let Some(sid) = &thread.metadata.session_id {
+                        current_session_ids.insert(sid.clone());
+                    }
                     current_thread_ids.insert(thread.metadata.thread_id);
                     entries.push(thread.into());
                 }
@@ -1228,7 +1239,9 @@ impl Sidebar {
 
         notified_threads.retain(|id| current_thread_ids.contains(id));
 
-        self.last_accessed
+        self.thread_last_accessed
+            .retain(|id, _| current_session_ids.contains(id));
+        self.thread_last_message_sent_or_queued
             .retain(|id, _| current_thread_ids.contains(id));
 
         self.contents = SidebarContents {
@@ -2202,7 +2215,7 @@ impl Sidebar {
             session_id: metadata.session_id.clone(),
             workspace: workspace.clone(),
         });
-        self.record_thread_accessed(metadata.thread_id);
+        self.record_thread_access(&metadata.session_id);
 
         if metadata.session_id.is_some() {
             self.pending_thread_activation = Some(metadata.thread_id);
@@ -2271,7 +2284,7 @@ impl Sidebar {
                         session_id: target_session_id.clone(),
                         workspace: workspace_for_entry.clone(),
                     });
-                    sidebar.record_thread_accessed(metadata_thread_id);
+                    sidebar.record_thread_access(&target_session_id);
                     sidebar.update_entries(cx);
                 });
             }
@@ -3288,12 +3301,22 @@ impl Sidebar {
         }
     }
 
-    fn record_thread_accessed(&mut self, thread_id: ThreadId) {
-        self.last_accessed.insert(thread_id, Utc::now());
+    fn record_thread_access(&mut self, session_id: &Option<acp::SessionId>) {
+        if let Some(sid) = session_id {
+            self.thread_last_accessed.insert(sid.clone(), Utc::now());
+        }
+    }
+
+    fn record_thread_message_sent(&mut self, thread_id: &agent_ui::ThreadId) {
+        self.thread_last_message_sent_or_queued
+            .insert(*thread_id, Utc::now());
     }
 
     fn display_time(&self, metadata: &ThreadMetadata) -> DateTime<Utc> {
-        metadata.last_user_interaction
+        self.thread_last_message_sent_or_queued
+            .get(&metadata.thread_id)
+            .copied()
+            .unwrap_or(metadata.updated_at)
     }
 
     fn mru_threads_for_switcher(&self, cx: &App) -> Vec<ThreadSwitcherEntry> {
@@ -3356,16 +3379,28 @@ impl Sidebar {
             .collect();
 
         entries.sort_by(|a, b| {
-            let a_accessed = self.last_accessed.get(&a.metadata.thread_id);
-            let b_accessed = self.last_accessed.get(&b.metadata.thread_id);
+            let a_accessed = self.thread_last_accessed.get(&a.session_id);
+            let b_accessed = self.thread_last_accessed.get(&b.session_id);
+
             match (a_accessed, b_accessed) {
                 (Some(a_time), Some(b_time)) => b_time.cmp(a_time),
                 (Some(_), None) => std::cmp::Ordering::Less,
                 (None, Some(_)) => std::cmp::Ordering::Greater,
-                (None, None) => b
-                    .metadata
-                    .last_user_interaction
-                    .cmp(&a.metadata.last_user_interaction),
+                (None, None) => {
+                    let a_sent = self
+                        .thread_last_message_sent_or_queued
+                        .get(&a.metadata.thread_id);
+                    let b_sent = self
+                        .thread_last_message_sent_or_queued
+                        .get(&b.metadata.thread_id);
+
+                    match (a_sent, b_sent) {
+                        (Some(a_time), Some(b_time)) => b_time.cmp(a_time),
+                        (Some(_), None) => std::cmp::Ordering::Less,
+                        (None, Some(_)) => std::cmp::Ordering::Greater,
+                        (None, None) => b.metadata.updated_at.cmp(&a.metadata.updated_at),
+                    }
+                }
             }
         });
 
@@ -3463,7 +3498,7 @@ impl Sidebar {
                             mw.retain_active_workspace(cx);
                         });
                     }
-                    this.record_thread_accessed(metadata.thread_id);
+                    this.record_thread_access(&metadata.session_id);
                     this.active_entry = Some(ActiveEntry {
                         thread_id: metadata.thread_id,
                         session_id: metadata.session_id.clone(),

crates/sidebar/src/sidebar_tests.rs 🔗

@@ -271,7 +271,6 @@ fn save_thread_metadata(
             updated_at,
             created_at,
             worktree_paths,
-            last_user_interaction: updated_at,
             archived: false,
             remote_connection,
         };
@@ -306,7 +305,6 @@ fn save_thread_metadata_with_main_paths(
         updated_at,
         created_at: None,
         worktree_paths: WorktreePaths::from_path_lists(main_worktree_paths, folder_paths).unwrap(),
-        last_user_interaction: updated_at,
         archived: false,
         remote_connection: None,
     };
@@ -772,7 +770,6 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) {
                     title: Some("Completed thread".into()),
                     updated_at: Utc::now(),
                     created_at: Some(Utc::now()),
-                    last_user_interaction: Utc::now(),
                     archived: false,
                     remote_connection: None,
                 },
@@ -797,7 +794,6 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) {
                     title: Some("Running thread".into()),
                     updated_at: Utc::now(),
                     created_at: Some(Utc::now()),
-                    last_user_interaction: Utc::now(),
                     archived: false,
                     remote_connection: None,
                 },
@@ -822,7 +818,6 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) {
                     title: Some("Error thread".into()),
                     updated_at: Utc::now(),
                     created_at: Some(Utc::now()),
-                    last_user_interaction: Utc::now(),
                     archived: false,
                     remote_connection: None,
                 },
@@ -848,7 +843,6 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) {
                     title: Some("Waiting thread".into()),
                     updated_at: Utc::now(),
                     created_at: Some(Utc::now()),
-                    last_user_interaction: Utc::now(),
                     archived: false,
                     remote_connection: None,
                 },
@@ -874,7 +868,6 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) {
                     title: Some("Notified thread".into()),
                     updated_at: Utc::now(),
                     created_at: Some(Utc::now()),
-                    last_user_interaction: Utc::now(),
                     archived: false,
                     remote_connection: None,
                 },
@@ -3924,7 +3917,6 @@ async fn test_activate_archived_thread_with_saved_paths_activates_matching_works
                 worktree_paths: WorktreePaths::from_folder_paths(&PathList::new(&[PathBuf::from(
                     "/project-b",
                 )])),
-                last_user_interaction: Utc::now(),
                 archived: false,
                 remote_connection: None,
             },
@@ -3993,7 +3985,6 @@ async fn test_activate_archived_thread_cwd_fallback_with_matching_workspace(
                 worktree_paths: WorktreePaths::from_folder_paths(&PathList::new(&[
                     std::path::PathBuf::from("/project-b"),
                 ])),
-                last_user_interaction: Utc::now(),
                 archived: false,
                 remote_connection: None,
             },
@@ -4058,7 +4049,6 @@ async fn test_activate_archived_thread_no_paths_no_cwd_uses_active_workspace(
                 updated_at: Utc::now(),
                 created_at: None,
                 worktree_paths: WorktreePaths::default(),
-                last_user_interaction: Utc::now(),
                 archived: false,
                 remote_connection: None,
             },
@@ -4115,7 +4105,6 @@ async fn test_activate_archived_thread_saved_paths_opens_new_workspace(cx: &mut
                 updated_at: Utc::now(),
                 created_at: None,
                 worktree_paths: WorktreePaths::from_folder_paths(&path_list_b),
-                last_user_interaction: Utc::now(),
                 archived: false,
                 remote_connection: None,
             },
@@ -4173,7 +4162,6 @@ async fn test_activate_archived_thread_reuses_workspace_in_another_window(cx: &m
                 worktree_paths: WorktreePaths::from_folder_paths(&PathList::new(&[PathBuf::from(
                     "/project-b",
                 )])),
-                last_user_interaction: Utc::now(),
                 archived: false,
                 remote_connection: None,
             },
@@ -4254,7 +4242,6 @@ async fn test_activate_archived_thread_reuses_workspace_in_another_window_with_t
                 worktree_paths: WorktreePaths::from_folder_paths(&PathList::new(&[PathBuf::from(
                     "/project-b",
                 )])),
-                last_user_interaction: Utc::now(),
                 archived: false,
                 remote_connection: None,
             },
@@ -4338,7 +4325,6 @@ async fn test_activate_archived_thread_prefers_current_window_for_matching_paths
                 worktree_paths: WorktreePaths::from_folder_paths(&PathList::new(&[PathBuf::from(
                     "/project-a",
                 )])),
-                last_user_interaction: Utc::now(),
                 archived: false,
                 remote_connection: None,
             },
@@ -5264,8 +5250,6 @@ async fn test_archive_last_worktree_thread_not_blocked_by_remote_thread_at_same_
             worktree_paths: WorktreePaths::from_folder_paths(&PathList::new(&[PathBuf::from(
                 "/wt-feature-a",
             )])),
-            last_user_interaction: chrono::TimeZone::with_ymd_and_hms(&Utc, 2024, 1, 1, 0, 0, 0)
-                .unwrap(),
             archived: false,
             remote_connection: Some(remote_host),
         };
@@ -5570,6 +5554,8 @@ async fn test_thread_switcher_ordering(cx: &mut TestAppContext) {
     cx.run_until_parked();
     assert_eq!(switcher_selected_id(&sidebar, cx), session_id_c);
 
+    assert!(sidebar.update(cx, |sidebar, _cx| sidebar.thread_last_accessed.is_empty()));
+
     // Confirm on Thread C.
     sidebar.update_in(cx, |sidebar, window, cx| {
         let switcher = sidebar.thread_switcher.as_ref().unwrap();
@@ -5586,7 +5572,14 @@ async fn test_thread_switcher_ordering(cx: &mut TestAppContext) {
         );
     });
 
-    sidebar.read_with(cx, |sidebar, _cx| {
+    sidebar.update(cx, |sidebar, _cx| {
+        let last_accessed = sidebar
+            .thread_last_accessed
+            .keys()
+            .cloned()
+            .collect::<Vec<_>>();
+        assert_eq!(last_accessed.len(), 1);
+        assert!(last_accessed.contains(&session_id_c));
         assert!(
             is_active_session(&sidebar, &session_id_c),
             "active_entry should be Thread({session_id_c:?})"
@@ -5615,7 +5608,15 @@ async fn test_thread_switcher_ordering(cx: &mut TestAppContext) {
     });
     cx.run_until_parked();
 
-    sidebar.read_with(cx, |sidebar, _cx| {
+    sidebar.update(cx, |sidebar, _cx| {
+        let last_accessed = sidebar
+            .thread_last_accessed
+            .keys()
+            .cloned()
+            .collect::<Vec<_>>();
+        assert_eq!(last_accessed.len(), 2);
+        assert!(last_accessed.contains(&session_id_c));
+        assert!(last_accessed.contains(&session_id_a));
         assert!(
             is_active_session(&sidebar, &session_id_a),
             "active_entry should be Thread({session_id_a:?})"
@@ -5650,7 +5651,16 @@ async fn test_thread_switcher_ordering(cx: &mut TestAppContext) {
     });
     cx.run_until_parked();
 
-    sidebar.read_with(cx, |sidebar, _cx| {
+    sidebar.update(cx, |sidebar, _cx| {
+        let last_accessed = sidebar
+            .thread_last_accessed
+            .keys()
+            .cloned()
+            .collect::<Vec<_>>();
+        assert_eq!(last_accessed.len(), 3);
+        assert!(last_accessed.contains(&session_id_c));
+        assert!(last_accessed.contains(&session_id_a));
+        assert!(last_accessed.contains(&session_id_b));
         assert!(
             is_active_session(&sidebar, &session_id_b),
             "active_entry should be Thread({session_id_b:?})"
@@ -6003,7 +6013,6 @@ async fn test_unarchive_first_thread_in_group_does_not_create_spurious_draft(
                     updated_at: Utc::now(),
                     created_at: None,
                     worktree_paths: WorktreePaths::from_folder_paths(&path_list_b),
-                    last_user_interaction: Utc::now(),
                     archived: true,
                     remote_connection: None,
                 },
@@ -6096,7 +6105,6 @@ async fn test_unarchive_into_new_workspace_does_not_create_duplicate_real_thread
                     updated_at: Utc::now(),
                     created_at: None,
                     worktree_paths: WorktreePaths::from_folder_paths(&path_list_b),
-                    last_user_interaction: Utc::now(),
                     archived: true,
                     remote_connection: None,
                 },
@@ -6324,7 +6332,6 @@ async fn test_unarchive_into_inactive_existing_workspace_does_not_leave_active_d
                     worktree_paths: WorktreePaths::from_folder_paths(&PathList::new(&[
                         PathBuf::from("/project-b"),
                     ])),
-                    last_user_interaction: Utc::now(),
                     archived: true,
                     remote_connection: None,
                 },
@@ -7169,7 +7176,6 @@ async fn test_unarchive_linked_worktree_thread_into_project_group_shows_only_res
                         folder_paths.clone(),
                     )
                     .expect("main and folder paths should be well-formed"),
-                    last_user_interaction: Utc::now(),
                     archived: true,
                     remote_connection: None,
                 },
@@ -7829,8 +7835,6 @@ async fn test_legacy_thread_with_canonical_path_opens_main_repo_workspace(cx: &m
             worktree_paths: WorktreePaths::from_folder_paths(&PathList::new(&[PathBuf::from(
                 "/project",
             )])),
-            last_user_interaction: chrono::TimeZone::with_ymd_and_hms(&Utc, 2024, 1, 1, 0, 0, 0)
-                .unwrap(),
             archived: false,
             remote_connection: None,
         };
@@ -8813,7 +8817,6 @@ mod property_test {
             updated_at,
             created_at: None,
             worktree_paths: WorktreePaths::from_path_lists(main_worktree_paths, path_list).unwrap(),
-            last_user_interaction: updated_at,
             archived: false,
             remote_connection: None,
         };
@@ -9716,8 +9719,6 @@ async fn test_remote_project_integration_does_not_briefly_render_as_separate_pro
                 PathList::new(&[PathBuf::from("/project-wt-1")]),
             )
             .unwrap(),
-            last_user_interaction: chrono::TimeZone::with_ymd_and_hms(&Utc, 2024, 1, 1, 0, 0, 1)
-                .unwrap(),
             archived: false,
             remote_connection,
         };