docs: Clean up old plan document (#51926)

Ben Brandt created

## Context

Shouldn't have been committed in the first place.

## Self-Review Checklist

<!-- Check before requesting review: -->
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- N/A

Change summary

docs/acp-threads-in-sidebar-plan.md | 580 ------------------------------
1 file changed, 580 deletions(-)

Detailed changes

docs/acp-threads-in-sidebar-plan.md 🔗

@@ -1,580 +0,0 @@
-# Plan: Show ACP Threads in the Sidebar (Revised)
-
-## Problem
-
-The sidebar currently only shows **Zed-native agent threads** (from `ThreadStore`/`ThreadsDatabase`). ACP threads (Claude Code, Codex, Gemini, etc.) are invisible in the sidebar once they're no longer live.
-
-## Root Cause
-
-`ThreadStore` and `ThreadsDatabase` only persist metadata for native threads. When `rebuild_contents` populates the sidebar, it reads from `ThreadStore` for historical threads and overlays live info from the `AgentPanel` — but non-native threads never get written to `ThreadStore`, so once they stop being live, they disappear.
-
-## Solution Overview (Revised)
-
-**Key change from the original plan:** We completely remove the sidebar's dependency on `ThreadStore`. Instead, the `Sidebar` itself owns a **single, unified persistence layer** — a new `SidebarDb` domain stored in the workspace DB — that tracks metadata for _all_ thread types (native and ACP). The sidebar becomes the single source of truth for what threads appear in the list.
-
-### Why Remove the ThreadStore Dependency?
-
-1. **Single responsibility** — The sidebar is the only consumer of "which threads to show in the list." Having it depend on `ThreadStore` (which exists primarily for native agent save/load) creates an indirect coupling that makes ACP integration awkward.
-2. **No merge logic** — The original plan required merging native `ThreadStore` data with a separate `AcpThreadMetadataDb` in `ThreadStore::reload`. By moving all sidebar metadata into one place, there's nothing to merge.
-3. **Simpler data flow** — Writers (native agent, ACP connections) push metadata to the sidebar DB. The sidebar reads from one table. No cross-crate coordination needed.
-4. **ThreadStore stays focused** — `ThreadStore` continues to manage native thread blob storage (save/load message data) without being polluted with sidebar display concerns.
-
-### Architecture
-
-```
-  ┌─────────────────────┐      ┌─────────────────────────┐
-  │    NativeAgent      │      │   ACP Connections       │
-  │  (on save_thread)   │      │ (on create/update/list) │
-  └──────────┬──────────┘      └──────────┬──────────────┘
-             │                            │
-             │   save_sidebar_thread()    │
-             └──────────┬─────────────────┘
-                        ▼
-              ┌───────────────────┐
-              │   SidebarDb       │
-              │  (workspace DB)   │
-              │  sidebar_threads  │
-              └────────┬──────────┘
-                       │
-                       ▼
-              ┌───────────────────┐
-              │     Sidebar       │
-              │ rebuild_contents  │
-              └───────────────────┘
-```
-
----
-
-## Step 1: Create `SidebarDb` Domain in `sidebar.rs`
-
-**File:** `crates/agent_ui/src/sidebar.rs`
-
-Add a `SidebarDb` domain using `db::static_connection!`, co-located in the sidebar module (or a small `persistence` submodule within `sidebar.rs` if it helps organization, but keeping it in the same file is fine for now).
-
-### Schema
-
-```rust
-use db::{
-    sqlez::{
-        bindable::Column, domain::Domain, statement::Statement,
-        thread_safe_connection::ThreadSafeConnection,
-    },
-    sqlez_macros::sql,
-};
-
-/// Lightweight metadata for any thread (native or ACP), enough to populate
-/// the sidebar list and route to the correct load path when clicked.
-#[derive(Debug, Clone)]
-pub struct SidebarThreadRow {
-    pub session_id: acp::SessionId,
-    /// `None` for native Zed threads, `Some("claude-code")` etc. for ACP agents.
-    pub agent_name: Option<String>,
-    pub title: SharedString,
-    pub updated_at: DateTime<Utc>,
-    pub created_at: Option<DateTime<Utc>>,
-    pub folder_paths: PathList,
-}
-
-pub struct SidebarDb(ThreadSafeConnection);
-
-impl Domain for SidebarDb {
-    const NAME: &str = stringify!(SidebarDb);
-
-    const MIGRATIONS: &[&str] = &[sql!(
-        CREATE TABLE IF NOT EXISTS sidebar_threads(
-            session_id TEXT PRIMARY KEY,
-            agent_name TEXT,
-            title TEXT NOT NULL,
-            updated_at TEXT NOT NULL,
-            created_at TEXT,
-            folder_paths TEXT,
-            folder_paths_order TEXT
-        ) STRICT;
-    )];
-}
-
-db::static_connection!(SIDEBAR_DB, SidebarDb, []);
-```
-
-### CRUD Methods
-
-```rust
-impl SidebarDb {
-    /// Upsert metadata for a thread (native or ACP).
-    pub async fn save(&self, row: &SidebarThreadRow) -> Result<()> {
-        let id = row.session_id.0.clone();
-        let agent_name = row.agent_name.clone();
-        let title = row.title.to_string();
-        let updated_at = row.updated_at.to_rfc3339();
-        let created_at = row.created_at.map(|dt| dt.to_rfc3339());
-        let serialized = row.folder_paths.serialize();
-        let (fp, fpo) = if row.folder_paths.is_empty() {
-            (None, None)
-        } else {
-            (Some(serialized.paths), Some(serialized.order))
-        };
-
-        self.write(move |conn| {
-            let mut stmt = Statement::prepare(
-                conn,
-                "INSERT INTO sidebar_threads(session_id, agent_name, title, updated_at, created_at, folder_paths, folder_paths_order)
-                 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
-                 ON CONFLICT(session_id) DO UPDATE SET
-                     agent_name = excluded.agent_name,
-                     title = excluded.title,
-                     updated_at = excluded.updated_at,
-                     folder_paths = excluded.folder_paths,
-                     folder_paths_order = excluded.folder_paths_order",
-            )?;
-            let mut i = stmt.bind(&id, 1)?;
-            i = stmt.bind(&agent_name, i)?;
-            i = stmt.bind(&title, i)?;
-            i = stmt.bind(&updated_at, i)?;
-            i = stmt.bind(&created_at, i)?;
-            i = stmt.bind(&fp, i)?;
-            stmt.bind(&fpo, i)?;
-            stmt.exec()
-        })
-        .await
-    }
-
-    /// List all sidebar thread metadata, ordered by updated_at descending.
-    pub fn list(&self) -> Result<Vec<SidebarThreadRow>> {
-        self.select::<SidebarThreadRow>(
-            "SELECT session_id, agent_name, title, updated_at, created_at, folder_paths, folder_paths_order
-             FROM sidebar_threads
-             ORDER BY updated_at DESC"
-        )?(())
-    }
-
-    /// List threads for a specific folder path set.
-    pub fn list_for_paths(&self, paths: &PathList) -> Result<Vec<SidebarThreadRow>> {
-        let serialized = paths.serialize();
-        self.select_bound::<String, SidebarThreadRow>(sql!(
-            SELECT session_id, agent_name, title, updated_at, created_at, folder_paths, folder_paths_order
-            FROM sidebar_threads
-            WHERE folder_paths = ?
-            ORDER BY updated_at DESC
-        ))?(serialized.paths)
-    }
-
-    /// Look up a single thread by session ID.
-    pub fn get(&self, session_id: &acp::SessionId) -> Result<Option<SidebarThreadRow>> {
-        let id = session_id.0.clone();
-        self.select_row_bound::<Arc<str>, SidebarThreadRow>(sql!(
-            SELECT session_id, agent_name, title, updated_at, created_at, folder_paths, folder_paths_order
-            FROM sidebar_threads
-            WHERE session_id = ?
-        ))?(id)
-    }
-
-    /// Return the total number of rows in the table.
-    pub fn count(&self) -> Result<usize> {
-        let count: (i32, i32) = self.select_row(sql!(
-            SELECT COUNT(*) FROM sidebar_threads
-        ))?(())?.unwrap_or_default();
-        Ok(count.0 as usize)
-    }
-
-    /// Delete metadata for a single thread.
-    pub async fn delete(&self, session_id: acp::SessionId) -> Result<()> {
-        let id = session_id.0;
-        self.write(move |conn| {
-            let mut stmt = Statement::prepare(
-                conn,
-                "DELETE FROM sidebar_threads WHERE session_id = ?",
-            )?;
-            stmt.bind(&id, 1)?;
-            stmt.exec()
-        })
-        .await
-    }
-
-    /// Delete all thread metadata.
-    pub async fn delete_all(&self) -> Result<()> {
-        self.write(move |conn| {
-            let mut stmt = Statement::prepare(
-                conn,
-                "DELETE FROM sidebar_threads",
-            )?;
-            stmt.exec()
-        })
-        .await
-    }
-}
-```
-
-### `Column` Implementation
-
-```rust
-impl Column for SidebarThreadRow {
-    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
-        let (id, next): (Arc<str>, i32) = Column::column(statement, start_index)?;
-        let (agent_name, next): (Option<String>, i32) = Column::column(statement, next)?;
-        let (title, next): (String, i32) = Column::column(statement, next)?;
-        let (updated_at_str, next): (String, i32) = Column::column(statement, next)?;
-        let (created_at_str, next): (Option<String>, i32) = Column::column(statement, next)?;
-        let (folder_paths_str, next): (Option<String>, i32) = Column::column(statement, next)?;
-        let (folder_paths_order_str, next): (Option<String>, i32) = Column::column(statement, next)?;
-
-        let updated_at = DateTime::parse_from_rfc3339(&updated_at_str)?.with_timezone(&Utc);
-        let created_at = created_at_str
-            .as_deref()
-            .map(DateTime::parse_from_rfc3339)
-            .transpose()?
-            .map(|dt| dt.with_timezone(&Utc));
-
-        let folder_paths = folder_paths_str
-            .map(|paths| {
-                PathList::deserialize(&util::path_list::SerializedPathList {
-                    paths,
-                    order: folder_paths_order_str.unwrap_or_default(),
-                })
-            })
-            .unwrap_or_default();
-
-        Ok((
-            SidebarThreadRow {
-                session_id: acp::SessionId::new(id),
-                agent_name,
-                title: title.into(),
-                updated_at,
-                created_at,
-                folder_paths,
-            },
-            next,
-        ))
-    }
-}
-```
-
-**Key points:**
-
-- `SIDEBAR_DB` is a `LazyLock` static — initialized on first use, no manual connection management.
-- The `agent_name` column is `NULL` for native Zed threads and a string like `"claude-code"` for ACP agents. This replaces the `agent_type` field from the original plan.
-- The DB file lives alongside other `static_connection!` databases.
-- `ThreadsDatabase` and `ThreadStore` are **completely unchanged** by this step.
-
----
-
-## Step 2: Replace `ThreadStore` Reads in `rebuild_contents` with `SidebarDb` Reads
-
-**File:** `crates/agent_ui/src/sidebar.rs`
-
-### Remove `ThreadStore` Dependency
-
-1. **Remove** `ThreadStore::global(cx)` and `ThreadStore::try_global(cx)` from `Sidebar::new` and `rebuild_contents`.
-2. **Remove** the `cx.observe_in(&thread_store, ...)` subscription that triggers `update_entries` when `ThreadStore` changes.
-3. **Replace** `thread_store.read(cx).threads_for_paths(&path_list)` calls with `SIDEBAR_DB.list_for_paths(&path_list)` (or read all rows once at the top of `rebuild_contents` and index them in memory, which is simpler and avoids repeated DB calls).
-
-### New Data Flow in `rebuild_contents`
-
-```rust
-fn rebuild_contents(&mut self, cx: &App) {
-    // ... existing workspace iteration setup ...
-
-    // Read ALL sidebar thread metadata once, index by folder_paths.
-    let all_sidebar_threads = SIDEBAR_DB.list().unwrap_or_default();
-    let mut threads_by_paths: HashMap<PathList, Vec<SidebarThreadRow>> = HashMap::new();
-    for row in all_sidebar_threads {
-        threads_by_paths
-            .entry(row.folder_paths.clone())
-            .or_default()
-            .push(row);
-    }
-
-    for (ws_index, workspace) in workspaces.iter().enumerate() {
-        // ... existing absorbed-workspace logic ...
-
-        let path_list = workspace_path_list(workspace, cx);
-
-        if should_load_threads {
-            let mut seen_session_ids: HashSet<acp::SessionId> = HashSet::new();
-
-            // Read from SidebarDb instead of ThreadStore
-            if let Some(rows) = threads_by_paths.get(&path_list) {
-                for row in rows {
-                    seen_session_ids.insert(row.session_id.clone());
-                    let (agent, icon) = match &row.agent_name {
-                        None => (Agent::NativeAgent, IconName::ZedAgent),
-                        Some(name) => (
-                            Agent::Custom { name: name.clone().into() },
-                            IconName::ZedAgent, // placeholder, resolved in Step 5
-                        ),
-                    };
-                    threads.push(ThreadEntry {
-                        agent,
-                        session_info: AgentSessionInfo {
-                            session_id: row.session_id.clone(),
-                            cwd: None,
-                            title: Some(row.title.clone()),
-                            updated_at: Some(row.updated_at),
-                            created_at: row.created_at,
-                            meta: None,
-                        },
-                        icon,
-                        icon_from_external_svg: None,
-                        status: AgentThreadStatus::default(),
-                        workspace: ThreadEntryWorkspace::Open(workspace.clone()),
-                        is_live: false,
-                        is_background: false,
-                        highlight_positions: Vec::new(),
-                        worktree_name: None,
-                        worktree_highlight_positions: Vec::new(),
-                        diff_stats: DiffStats::default(),
-                    });
-                }
-            }
-
-            // ... existing linked git worktree logic, also reading from threads_by_paths ...
-            // ... existing live thread overlay logic (unchanged) ...
-        }
-    }
-}
-```
-
-### What Changes
-
-- `rebuild_contents` reads from `SIDEBAR_DB` instead of `ThreadStore`.
-- The `ThreadEntry.agent` field now carries `Agent::Custom { name }` for ACP threads, enabling correct routing in `activate_thread`.
-- The live thread overlay logic (from `all_thread_infos_for_workspace`) is **unchanged** — it still reads from `AgentPanel` to get real-time status of running threads.
-
-### What Stays the Same
-
-- The entire workspace/absorbed-workspace/git-worktree structure.
-- The live thread overlay pass.
-- The notification tracking logic.
-- The search/filter logic.
-
----
-
-## Step 3: Write Native Thread Metadata to `SidebarDb`
-
-**File:** `crates/agent_ui/src/sidebar.rs` and/or `crates/agent_ui/src/agent_panel.rs`
-
-When a native thread is saved (after conversation, on title update, etc.), we also write its metadata to `SidebarDb`. There are two approaches:
-
-### Option A: Subscribe to `ThreadStore` Changes (Recommended)
-
-Keep a one-directional sync: when `ThreadStore` finishes a `save_thread` or `reload`, the sidebar syncs the metadata to `SidebarDb`. This can be done in the sidebar's workspace subscription or by observing `ThreadStore` changes purely for the purpose of syncing (not for reading).
-
-```rust
-// In Sidebar::subscribe_to_workspace or a dedicated sync method:
-fn sync_native_threads_to_sidebar_db(&self, cx: &App) {
-    if let Some(thread_store) = ThreadStore::try_global(cx) {
-        let entries: Vec<_> = thread_store.read(cx).entries().collect();
-        cx.background_spawn(async move {
-            for meta in entries {
-                SIDEBAR_DB.save(&SidebarThreadRow {
-                    session_id: meta.id,
-                    agent_name: None, // native
-                    title: meta.title,
-                    updated_at: meta.updated_at,
-                    created_at: meta.created_at,
-                    folder_paths: meta.folder_paths,
-                }).await.log_err();
-            }
-        }).detach();
-    }
-}
-```
-
-### Option B: Write at the Point of Save
-
-In `AgentPanel` or wherever `thread_store.save_thread()` is called, also call `SIDEBAR_DB.save(...)`. This is more direct but requires touching more call sites.
-
-**Recommendation:** Option A is simpler for the initial implementation. We observe `ThreadStore` changes, diff against `SidebarDb`, and sync. Later, if we want to remove `ThreadStore` entirely from the write path for native threads, we can switch to Option B.
-
----
-
-## Step 4: Write ACP Thread Metadata to `SidebarDb`
-
-**File:** `crates/agent_ui/src/connection_view.rs` (or `agent_panel.rs`)
-
-When ACP sessions are created, updated, or listed, write metadata directly to `SidebarDb`:
-
-- **On new session creation:** After `connection.new_session()` returns the `AcpThread`, call `SIDEBAR_DB.save(...)`.
-- **On title update:** ACP threads receive title updates via `SessionInfoUpdate`. When these come in, call `SIDEBAR_DB.save(...)` with the new title and updated timestamp.
-- **On session list refresh:** When `AgentSessionList::list_sessions` returns for an ACP agent, bulk-sync the metadata into `SidebarDb`.
-
-After any write, call `cx.notify()` on the `Sidebar` entity (or use a channel/event) to trigger a `rebuild_contents`.
-
-### Triggering Sidebar Refresh
-
-Since the sidebar no longer observes `ThreadStore`, we need a mechanism to trigger `rebuild_contents` after DB writes. Options:
-
-1. **Emit an event from `AgentPanel`** — The sidebar already subscribes to `AgentPanelEvent`. Add a new variant like `AgentPanelEvent::ThreadMetadataChanged` and emit it after saving to `SidebarDb`.
-2. **Use `cx.notify()` directly** — If the save happens within a `Sidebar` method, just call `self.update_entries(cx)`.
-3. **Observe a lightweight signal entity** — A simple `Entity<()>` that gets notified after DB writes.
-
-**Recommendation:** Option 1 (emit from `AgentPanel`) is cleanest since the sidebar already subscribes to panel events.
-
----
-
-## Step 5: Handle Agent Icon Resolution for ACP Threads
-
-**File:** `crates/agent_ui/src/sidebar.rs`
-
-For ACP threads in the sidebar, we need the correct agent icon. The `agent_name` string stored in `SidebarDb` maps to an agent in the `AgentServerStore`, which has icon info.
-
-In `rebuild_contents`, after building the initial thread list from `SidebarDb`, resolve icons for ACP threads:
-
-```rust
-// For ACP threads, look up the icon from the agent server store
-if let Some(name) = &row.agent_name {
-    if let Some(agent_server_store) = /* get from workspace */ {
-        // resolve icon from agent_server_store using name
-    }
-}
-```
-
----
-
-## Step 6: Handle Delete Operations Correctly
-
-**File:** `crates/agent_ui/src/sidebar.rs`
-
-When the user deletes a thread from the sidebar:
-
-- **All threads** → Delete from `SidebarDb` via `SIDEBAR_DB.delete(session_id)`.
-- **Native threads** → _Also_ delete from `ThreadStore`/`ThreadsDatabase` (to clean up the blob data).
-- **ACP threads** → Optionally notify the ACP server via `AgentSessionList::delete_session`.
-
-The `agent_name` field on `SidebarThreadRow` (or the `Agent` enum on `ThreadEntry`) tells us which path to take.
-
-When the user clears all history:
-
-```rust
-// Delete all sidebar metadata
-SIDEBAR_DB.delete_all().await?;
-// Also clear native thread blobs
-thread_store.delete_threads(cx);
-// Optionally notify ACP servers
-```
-
----
-
-## Step 7: Handle `activate_thread` Routing
-
-**File:** `crates/agent_ui/src/sidebar.rs`, `crates/agent_ui/src/agent_panel.rs`
-
-In `activate_thread`, branch on the `Agent` variant:
-
-- `Agent::NativeAgent` → Call `panel.load_agent_thread(Agent::NativeAgent, session_id, ...)` (current behavior).
-- `Agent::Custom { name }` → Call `panel.load_agent_thread(Agent::Custom { name }, session_id, ...)` so it routes to the correct `AgentConnection::load_session`.
-
-This is already partially set up — `activate_thread` takes an `Agent` parameter. The key change is that `ThreadEntry` now carries the correct `Agent` variant based on `SidebarThreadRow.agent_name`.
-
----
-
-## Step 8: Handle `activate_archived_thread` Without ThreadStore
-
-**File:** `crates/agent_ui/src/sidebar.rs`
-
-Currently, `activate_archived_thread` looks up `saved_path_list` from `ThreadStore`:
-
-```rust
-let saved_path_list = ThreadStore::try_global(cx).and_then(|thread_store| {
-    thread_store
-        .read(cx)
-        .thread_from_session_id(&session_info.session_id)
-        .map(|thread| thread.folder_paths.clone())
-});
-```
-
-Replace this with a targeted `SidebarDb::get` lookup (single-row SELECT, no full table scan):
-
-```rust
-let saved_path_list = SIDEBAR_DB
-    .get(&session_info.session_id)
-    .ok()
-    .flatten()
-    .map(|row| row.folder_paths);
-```
-
----
-
-## Step 9: Error Handling for Offline Agents
-
-When an ACP thread is clicked but the agent server is not running:
-
-- Show a toast/notification explaining the agent is offline.
-- Keep the metadata in the sidebar (don't remove it).
-- Optionally offer to start the agent server.
-
----
-
-## Step 10: Migration — Backfill Existing Native Threads
-
-On first launch after this change, the `SidebarDb` will be empty while `ThreadsDatabase` has existing native threads. We need a one-time backfill:
-
-```rust
-// In Sidebar::new or a dedicated init method:
-fn backfill_native_threads_if_needed(cx: &App) {
-    if SIDEBAR_DB.count()  > 0 {
-        return; // Already populated
-    }
-
-    if let Some(thread_store) = ThreadStore::try_global(cx) {
-        let entries: Vec<_> = thread_store.read(cx).entries().collect();
-        cx.background_spawn(async move {
-            for meta in entries {
-                SIDEBAR_DB.save(&SidebarThreadRow {
-                    session_id: meta.id,
-                    agent_name: None,
-                    title: meta.title,
-                    updated_at: meta.updated_at,
-                    created_at: meta.created_at,
-                    folder_paths: meta.folder_paths,
-                }).await.log_err();
-            }
-        }).detach();
-    }
-}
-```
-
----
-
-## Summary of Files to Change
-
-| File                                     | Changes                                                                                                                                                                                                                                                        |
-| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `crates/agent_ui/Cargo.toml`             | Add `db.workspace = true`, `sqlez.workspace = true`, `sqlez_macros.workspace = true`, `chrono.workspace = true` dependencies                                                                                                                                   |
-| `crates/agent_ui/src/sidebar.rs`         | **Main changes.** Add `SidebarDb` domain + `SIDEBAR_DB` static + `SidebarThreadRow`. Replace all `ThreadStore` reads in `rebuild_contents` with `SidebarDb` reads. Update `activate_archived_thread`. Add native thread sync logic. Add backfill on first run. |
-| `crates/agent_ui/src/agent_panel.rs`     | Emit `AgentPanelEvent::ThreadMetadataChanged` after thread saves. Potentially write ACP metadata to `SidebarDb` here.                                                                                                                                          |
-| `crates/agent_ui/src/connection_view.rs` | Write ACP metadata to `SidebarDb` on session creation, title updates, and session list refreshes.                                                                                                                                                              |
-
-## What Is NOT Changed
-
-| File / Area                                | Why                                                                                                                          |
-| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
-| `threads` table schema                     | No migration needed — native blob persistence is completely untouched                                                        |
-| `ThreadsDatabase` methods                  | `save_thread_sync`, `load_thread`, `list_threads`, `delete_thread`, `delete_threads` — all unchanged                         |
-| `ThreadStore` struct/methods               | Stays exactly as-is. It's still used for native thread blob save/load. The sidebar just no longer reads from it for display. |
-| `NativeAgent::load_thread` / `open_thread` | These deserialize `DbThread` blobs — completely unaffected                                                                   |
-| `crates/acp_thread/`                       | No new persistence module needed there (unlike the original plan)                                                            |
-| `crates/agent/src/db.rs`                   | `DbThreadMetadata` is unchanged — no `agent_type` field added                                                                |
-
-## Execution Order
-
-1. **SidebarDb domain** (Step 1) — Create `SidebarDb`, `SidebarThreadRow`, `SIDEBAR_DB` static, CRUD methods in `sidebar.rs`.
-2. **Replace reads** (Step 2) — Swap `ThreadStore` reads in `rebuild_contents` for `SidebarDb` reads.
-3. **Native write path** (Step 3) — Sync native thread metadata from `ThreadStore` into `SidebarDb`.
-4. **ACP write path** (Step 4) — Write ACP thread metadata to `SidebarDb` from connection views.
-5. **Icon resolution** (Step 5) — Resolve ACP agent icons in the sidebar.
-6. **Delete path** (Step 6) — Route deletes to `SidebarDb` + native blob cleanup + ACP server notification.
-7. **Activate routing** (Step 7) — Ensure `activate_thread` routes correctly based on `Agent` variant.
-8. **Archive fix** (Step 8) — Update `activate_archived_thread` to use `SidebarDb`.
-9. **Migration** (Step 10) — Backfill existing native threads on first run.
-10. **Polish** (Step 9) — Error handling for offline agents.
-
-## Key Differences from Original Plan
-
-| Aspect                               | Original Plan                                                                              | Revised Plan                                                                    |
-| ------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
-| **Where ACP metadata lives**         | New `AcpThreadMetadataDb` in `crates/acp_thread/`                                          | `SidebarDb` in `crates/agent_ui/src/sidebar.rs`                                 |
-| **Where sidebar reads from**         | `ThreadStore` (which merges native + ACP)                                                  | `SidebarDb` directly (single source)                                            |
-| **ThreadStore changes**              | Added `agent_type` to `DbThreadMetadata`, merge logic in `reload`, new save/delete methods | **None** — ThreadStore is untouched                                             |
-| **`crates/agent/src/db.rs` changes** | Added `agent_type: Option<String>` to `DbThreadMetadata`                                   | **None**                                                                        |
-| **Merge complexity**                 | Two data sources merged in `ThreadStore::reload`                                           | No merge — one table, one read                                                  |
-| **Crate dependencies**               | `acp_thread` gains `db` dependency                                                         | `agent_ui` gains `db` dependency (more natural — it's a UI persistence concern) |