From 198849ddaa143dd4c11ed49dab4a9f0561e45af1 Mon Sep 17 00:00:00 2001 From: Bambang Tri Rahmat Doni Date: Wed, 2 Sep 2026 15:18:03 +0700 Subject: [PATCH 1/7] refactor(shell): rename thread domain types and state to atom Align left-sidebar data models, callbacks, and view-model APIs with atom terminology so each sidebar row represents an atom rather than a thread. --- .../shell/left_sidebar/content/callbacks.rs | 12 +- .../shell/left_sidebar/content/pinned_drag.rs | 28 +- src/app/shell/left_sidebar/demo_data.rs | 112 ++-- src/app/shell/left_sidebar/state.rs | 486 +++++++++--------- src/app/shell/left_sidebar/surfaces.rs | 18 +- 5 files changed, 328 insertions(+), 328 deletions(-) diff --git a/src/app/shell/left_sidebar/content/callbacks.rs b/src/app/shell/left_sidebar/content/callbacks.rs index cda4493..53096f6 100644 --- a/src/app/shell/left_sidebar/content/callbacks.rs +++ b/src/app/shell/left_sidebar/content/callbacks.rs @@ -4,9 +4,9 @@ use std::rc::Rc; use gpui::{App, Window}; -pub type ThreadIdCallback = Rc; -pub type ThreadSelectCallback = Rc; -pub type ThreadHoverCallback = Rc, &mut Window, &mut App)>; -pub type ThreadMoveCallback = Rc; -pub type ThreadDragOverCallback = Rc; -pub type ThreadDropCallback = Rc; +pub type AtomIdCallback = Rc; +pub type AtomSelectCallback = Rc; +pub type AtomHoverCallback = Rc, &mut Window, &mut App)>; +pub type AtomMoveCallback = Rc; +pub type AtomDragOverCallback = Rc; +pub type AtomDropCallback = Rc; diff --git a/src/app/shell/left_sidebar/content/pinned_drag.rs b/src/app/shell/left_sidebar/content/pinned_drag.rs index 7dc0fe2..de586f2 100644 --- a/src/app/shell/left_sidebar/content/pinned_drag.rs +++ b/src/app/shell/left_sidebar/content/pinned_drag.rs @@ -1,24 +1,24 @@ -//! Drag-and-drop payload and drop-indicator state for thread row reordering. +//! Drag-and-drop payload and drop-indicator state for atom row reordering. -use super::super::demo_data::ThreadShelf; +use super::super::demo_data::AtomShelf; #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ThreadDragScope { +pub enum AtomDragScope { Pinned, Active, Settled, Archived, } -impl ThreadDragScope { - pub fn from_thread(_thread_id: &str, effective_shelf: ThreadShelf, is_archived: bool) -> Self { +impl AtomDragScope { + pub fn from_atom(_atom_id: &str, effective_shelf: AtomShelf, is_archived: bool) -> Self { if is_archived { return Self::Archived; } match effective_shelf { - ThreadShelf::Pinned => Self::Pinned, - ThreadShelf::Active => Self::Active, - ThreadShelf::Settled => Self::Settled, + AtomShelf::Pinned => Self::Pinned, + AtomShelf::Active => Self::Active, + AtomShelf::Settled => Self::Settled, } } @@ -28,9 +28,9 @@ impl ThreadDragScope { } #[derive(Clone, Debug)] -pub struct PinnedThreadDrag { - pub thread_id: String, - pub scope: ThreadDragScope, +pub struct PinnedAtomDrag { + pub atom_id: String, + pub scope: AtomDragScope, pub title: gpui::SharedString, pub preview_bg: gpui::Hsla, pub preview_text: gpui::Hsla, @@ -50,13 +50,13 @@ pub struct PinnedDragState { } impl PinnedDragState { - pub fn for_thread(&self, thread_id: &str) -> PinnedRowDragUi { + pub fn for_atom(&self, atom_id: &str) -> PinnedRowDragUi { let (drop_above, drop_below) = match &self.drop_target { - Some((id, insert_after)) if id == thread_id => (!*insert_after, *insert_after), + Some((id, insert_after)) if id == atom_id => (!*insert_after, *insert_after), _ => (false, false), }; PinnedRowDragUi { - is_source: self.dragging_id.as_deref() == Some(thread_id), + is_source: self.dragging_id.as_deref() == Some(atom_id), drop_above, drop_below, } diff --git a/src/app/shell/left_sidebar/demo_data.rs b/src/app/shell/left_sidebar/demo_data.rs index d23e694..a609e4d 100644 --- a/src/app/shell/left_sidebar/demo_data.rs +++ b/src/app/shell/left_sidebar/demo_data.rs @@ -1,14 +1,14 @@ //! Static demo projections for left-sidebar interface scaffolding. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ThreadShelf { +pub enum AtomShelf { Pinned, Active, Settled, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ThreadStatus { +pub enum AtomStatus { Working, Monitoring, Approval, @@ -18,7 +18,7 @@ pub enum ThreadStatus { Woke, } -impl ThreadStatus { +impl AtomStatus { pub fn label(self) -> Option<&'static str> { match self { Self::Working => Some("Working"), @@ -48,15 +48,15 @@ pub struct DemoDraft { } #[derive(Clone, Debug)] -pub struct DemoThread { +pub struct DemoAtom { pub id: &'static str, pub title: &'static str, pub project_key: &'static str, pub project_title: &'static str, pub branch: Option<&'static str>, - pub shelf: ThreadShelf, + pub shelf: AtomShelf, pub time_label: &'static str, - pub status: ThreadStatus, + pub status: AtomStatus, pub pr_number: Option, pub diff_insertions: Option, pub diff_deletions: Option, @@ -65,7 +65,7 @@ pub struct DemoThread { pub terminal_process_count: u32, } -pub const ALL_PROJECTS_LABEL: &str = "All projects"; +pub const ALL_ATOMS_LABEL: &str = "All atoms"; pub const DEMO_PROJECTS: [DemoProject; 2] = [ DemoProject { @@ -84,19 +84,19 @@ pub const DEMO_DRAFT: DemoDraft = DemoDraft { id: "draft-1", project_key: "opencore", project_title: "opencore_rustroops", - preview: "New thread draft…", + preview: "New atom draft…", }; -pub const DEMO_THREADS: [DemoThread; 15] = [ - DemoThread { +pub const DEMO_ATOMS: [DemoAtom; 15] = [ + DemoAtom { id: "pinned-1", title: "Fix dock layout persistence", project_key: "opencore", project_title: "opencore_rustroops", branch: Some("feat/shell-dock"), - shelf: ThreadShelf::Pinned, + shelf: AtomShelf::Pinned, time_label: "2h", - status: ThreadStatus::Working, + status: AtomStatus::Working, pr_number: Some(42), diff_insertions: Some(128), diff_deletions: Some(24), @@ -104,15 +104,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 1, }, - DemoThread { + DemoAtom { id: "pinned-2", - title: "Pinned thread drag reorder", + title: "Pinned atom drag reorder", project_key: "opencore", project_title: "opencore_rustroops", branch: Some("feat/left-sidebar"), - shelf: ThreadShelf::Pinned, + shelf: AtomShelf::Pinned, time_label: "45m", - status: ThreadStatus::Ready, + status: AtomStatus::Ready, pr_number: None, diff_insertions: None, diff_deletions: None, @@ -120,15 +120,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "active-1", title: "Implement left sidebar UI", project_key: "opencore", project_title: "opencore_rustroops", branch: Some("feat/left-sidebar"), - shelf: ThreadShelf::Active, + shelf: AtomShelf::Active, time_label: "now", - status: ThreadStatus::Ready, + status: AtomStatus::Ready, pr_number: None, diff_insertions: None, diff_deletions: None, @@ -136,15 +136,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "active-2", title: "Theme transition polish", project_key: "opencore", project_title: "opencore_rustroops", branch: Some("main"), - shelf: ThreadShelf::Active, + shelf: AtomShelf::Active, time_label: "18m", - status: ThreadStatus::Monitoring, + status: AtomStatus::Monitoring, pr_number: None, diff_insertions: Some(12), diff_deletions: Some(3), @@ -152,15 +152,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "active-3", title: "Approval gate on deploy", project_key: "sample_app", project_title: "sample_app", branch: Some("feat/deploy"), - shelf: ThreadShelf::Active, + shelf: AtomShelf::Active, time_label: "45m", - status: ThreadStatus::Approval, + status: AtomStatus::Approval, pr_number: Some(17), diff_insertions: None, diff_deletions: None, @@ -168,15 +168,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "active-4", title: "Review gpui-component dock APIs", project_key: "sample_app", project_title: "sample_app", branch: Some("research/dock"), - shelf: ThreadShelf::Active, + shelf: AtomShelf::Active, time_label: "2h", - status: ThreadStatus::Ready, + status: AtomStatus::Ready, pr_number: None, diff_insertions: None, diff_deletions: None, @@ -184,15 +184,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "settled-1", title: "Welcome view port", project_key: "opencore", project_title: "opencore_rustroops", branch: Some("main"), - shelf: ThreadShelf::Settled, + shelf: AtomShelf::Settled, time_label: "1d", - status: ThreadStatus::Ready, + status: AtomStatus::Ready, pr_number: Some(38), diff_insertions: None, diff_deletions: None, @@ -200,15 +200,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "settled-2", title: "Shell workspace title bar", project_key: "opencore", project_title: "opencore_rustroops", branch: Some("feat/shell"), - shelf: ThreadShelf::Settled, + shelf: AtomShelf::Settled, time_label: "3d", - status: ThreadStatus::Ready, + status: AtomStatus::Ready, pr_number: None, diff_insertions: None, diff_deletions: None, @@ -216,15 +216,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "settled-3", title: "Preferences persistence", project_key: "opencore", project_title: "opencore_rustroops", branch: None, - shelf: ThreadShelf::Settled, + shelf: AtomShelf::Settled, time_label: "1w", - status: ThreadStatus::Ready, + status: AtomStatus::Ready, pr_number: None, diff_insertions: None, diff_deletions: None, @@ -232,15 +232,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "settled-4", title: "Dock animation tween", project_key: "opencore", project_title: "opencore_rustroops", branch: Some("feat/dock-tween"), - shelf: ThreadShelf::Settled, + shelf: AtomShelf::Settled, time_label: "2w", - status: ThreadStatus::Ready, + status: AtomStatus::Ready, pr_number: Some(12), diff_insertions: None, diff_deletions: None, @@ -248,15 +248,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "settled-5", title: "Native menu bridge", project_key: "opencore", project_title: "opencore_rustroops", branch: Some("main"), - shelf: ThreadShelf::Settled, + shelf: AtomShelf::Settled, time_label: "2w", - status: ThreadStatus::Failed, + status: AtomStatus::Failed, pr_number: None, diff_insertions: None, diff_deletions: None, @@ -264,15 +264,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "settled-6", - title: "Thread sidebar search", + title: "atom sidebar search", project_key: "sample_app", project_title: "sample_app", branch: Some("feat/search"), - shelf: ThreadShelf::Settled, + shelf: AtomShelf::Settled, time_label: "3w", - status: ThreadStatus::Woke, + status: AtomStatus::Woke, pr_number: None, diff_insertions: None, diff_deletions: None, @@ -280,15 +280,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: true, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "settled-7", title: "Composer placeholder text", project_key: "opencore", project_title: "opencore_rustroops", branch: Some("main"), - shelf: ThreadShelf::Settled, + shelf: AtomShelf::Settled, time_label: "4w", - status: ThreadStatus::Ready, + status: AtomStatus::Ready, pr_number: None, diff_insertions: None, diff_deletions: None, @@ -296,15 +296,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "settled-8", title: "Right panel file tree", project_key: "sample_app", project_title: "sample_app", branch: Some("feat/files"), - shelf: ThreadShelf::Settled, + shelf: AtomShelf::Settled, time_label: "5w", - status: ThreadStatus::Ready, + status: AtomStatus::Ready, pr_number: Some(9), diff_insertions: None, diff_deletions: None, @@ -312,15 +312,15 @@ pub const DEMO_THREADS: [DemoThread; 15] = [ is_woke: false, terminal_process_count: 0, }, - DemoThread { + DemoAtom { id: "settled-9", title: "Keyboard shortcut map", project_key: "opencore", project_title: "opencore_rustroops", branch: Some("main"), - shelf: ThreadShelf::Settled, + shelf: AtomShelf::Settled, time_label: "6w", - status: ThreadStatus::Input, + status: AtomStatus::Input, pr_number: None, diff_insertions: None, diff_deletions: None, diff --git a/src/app/shell/left_sidebar/state.rs b/src/app/shell/left_sidebar/state.rs index 4fa24cd..cde0b9f 100644 --- a/src/app/shell/left_sidebar/state.rs +++ b/src/app/shell/left_sidebar/state.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use super::demo_data::{ - ALL_PROJECTS_LABEL, DEMO_DRAFT, DEMO_THREADS, DemoDraft, DemoThread, ThreadShelf, ThreadStatus, + ALL_ATOMS_LABEL, DEMO_DRAFT, DEMO_ATOMS, DemoDraft, DemoAtom, AtomShelf, AtomStatus, }; pub const SETTLED_PAGE_INITIAL: usize = 10; @@ -39,29 +39,29 @@ pub struct SidebarViewModel { pub settled_expanded: bool, pub archived_expanded: bool, pub settled_visible_limit: usize, - pub active_thread_id: String, - pub selected_thread_ids: HashSet, + pub active_atom_id: String, + pub selected_atom_ids: HashSet, pub pinned_order: Vec, pub active_order: Vec, pub settled_order: Vec, pub archived_order: Vec, - pub hovered_thread_id: Option, + pub hovered_atom_id: Option, pub footer_mode: FooterMode, pub footer_back_context: Option, pub show_update_pill: bool, pub draft_visible: bool, - pub thread_shelf_overrides: HashMap, - pub archived_thread_ids: HashSet, + pub atom_shelf_overrides: HashMap, + pub archived_atom_ids: HashSet, pub display_title_overrides: HashMap, - pub renaming_thread_id: Option, + pub renaming_atom_id: Option, } impl SidebarViewModel { - pub fn new(active_thread_id: impl Into) -> Self { - let active_thread_id = active_thread_id.into(); - let pinned_order = shelf_order_from_demo(ThreadShelf::Pinned); - let active_order = shelf_order_from_demo(ThreadShelf::Active); - let settled_order = shelf_order_from_demo(ThreadShelf::Settled); + pub fn new(active_atom_id: impl Into) -> Self { + let active_atom_id = active_atom_id.into(); + let pinned_order = shelf_order_from_demo(AtomShelf::Pinned); + let active_order = shelf_order_from_demo(AtomShelf::Active); + let settled_order = shelf_order_from_demo(AtomShelf::Settled); Self { search_query: String::new(), @@ -70,21 +70,21 @@ impl SidebarViewModel { settled_expanded: true, archived_expanded: false, settled_visible_limit: SETTLED_PAGE_INITIAL, - active_thread_id, - selected_thread_ids: HashSet::new(), + active_atom_id, + selected_atom_ids: HashSet::new(), pinned_order, active_order: active_order_with_draft(active_order, true), settled_order, archived_order: Vec::new(), - hovered_thread_id: None, + hovered_atom_id: None, footer_mode: FooterMode::Utilities, footer_back_context: None, show_update_pill: true, draft_visible: true, - thread_shelf_overrides: HashMap::new(), - archived_thread_ids: HashSet::new(), + atom_shelf_overrides: HashMap::new(), + archived_atom_ids: HashSet::new(), display_title_overrides: HashMap::new(), - renaming_thread_id: None, + renaming_atom_id: None, } } @@ -98,22 +98,22 @@ impl SidebarViewModel { self.footer_back_context = None; } - pub fn effective_shelf(&self, thread: &DemoThread) -> ThreadShelf { - self.thread_shelf_overrides - .get(thread.id) + pub fn effective_shelf(&self, atom: &DemoAtom) -> AtomShelf { + self.atom_shelf_overrides + .get(atom.id) .copied() - .unwrap_or(thread.shelf) + .unwrap_or(atom.shelf) } - pub fn is_archived(&self, thread: &DemoThread) -> bool { - self.archived_thread_ids.contains(thread.id) + pub fn is_archived(&self, atom: &DemoAtom) -> bool { + self.archived_atom_ids.contains(atom.id) } - pub fn display_title(&self, thread: &DemoThread) -> String { + pub fn display_title(&self, atom: &DemoAtom) -> String { self.display_title_overrides - .get(thread.id) + .get(atom.id) .cloned() - .unwrap_or_else(|| thread.title.to_string()) + .unwrap_or_else(|| atom.title.to_string()) } pub fn clear_search(&mut self) { @@ -126,18 +126,18 @@ impl SidebarViewModel { pub fn scoped_label(&self) -> &str { match &self.project_scope { - None => ALL_PROJECTS_LABEL, - Some(key) => DEMO_THREADS + None => ALL_ATOMS_LABEL, + Some(key) => DEMO_ATOMS .iter() .find(|t| t.project_key == key) .map(|t| t.project_title) - .unwrap_or(ALL_PROJECTS_LABEL), + .unwrap_or(ALL_ATOMS_LABEL), } } - pub fn visible_threads(&self) -> Vec<&DemoThread> { + pub fn visible_atoms(&self) -> Vec<&DemoAtom> { let query = self.search_query.trim().to_ascii_lowercase(); - DEMO_THREADS + DEMO_ATOMS .iter() .filter(|t| !self.is_archived(t)) .filter(|t| self.matches_scope(t)) @@ -147,25 +147,25 @@ impl SidebarViewModel { .collect() } - pub fn pinned_threads(&self) -> Vec<&DemoThread> { + pub fn pinned_atoms(&self) -> Vec<&DemoAtom> { self.pinned_order .iter() - .filter_map(|id| DEMO_THREADS.iter().find(|t| t.id == id)) + .filter_map(|id| DEMO_ATOMS.iter().find(|t| t.id == id)) .filter(|t| !self.is_archived(t)) .filter(|t| self.matches_scope(t)) - .filter(|t| self.effective_shelf(t) == ThreadShelf::Pinned) + .filter(|t| self.effective_shelf(t) == AtomShelf::Pinned) .filter(|_| !self.is_searching()) .collect() } - pub fn active_threads(&self) -> Vec<&DemoThread> { + pub fn active_atoms(&self) -> Vec<&DemoAtom> { if self.is_searching() { return Vec::new(); } - self.threads_in_order(&self.active_order, ThreadShelf::Active) + self.atoms_in_order(&self.active_order, AtomShelf::Active) } - /// Active section row ids (draft + inbox threads) in display order. + /// Active section row ids (draft + inbox atoms) in display order. pub fn active_section_ids(&self) -> Vec { if self.is_searching() { return Vec::new(); @@ -176,7 +176,7 @@ impl SidebarViewModel { if id.as_str() == DEMO_DRAFT.id { self.draft_visible } else { - self.active_threads().iter().any(|t| t.id == id.as_str()) + self.active_atoms().iter().any(|t| t.id == id.as_str()) } }) .cloned() @@ -184,48 +184,48 @@ impl SidebarViewModel { } pub fn is_draft_active(&self) -> bool { - self.active_thread_id == DEMO_DRAFT.id + self.active_atom_id == DEMO_DRAFT.id } pub fn is_draft_selected(&self) -> bool { - self.selected_thread_ids.contains(DEMO_DRAFT.id) + self.selected_atom_ids.contains(DEMO_DRAFT.id) } - pub fn settled_threads(&self) -> Vec<&DemoThread> { + pub fn settled_atoms(&self) -> Vec<&DemoAtom> { if self.is_searching() { return Vec::new(); } - self.threads_in_order(&self.settled_order, ThreadShelf::Settled) + self.atoms_in_order(&self.settled_order, AtomShelf::Settled) } - pub fn archived_threads(&self) -> Vec<&DemoThread> { + pub fn archived_atoms(&self) -> Vec<&DemoAtom> { if self.is_searching() { return Vec::new(); } self.archived_order .iter() - .filter_map(|id| DEMO_THREADS.iter().find(|t| t.id == id)) + .filter_map(|id| DEMO_ATOMS.iter().find(|t| t.id == id)) .filter(|t| self.is_archived(t)) .filter(|t| self.matches_scope(t)) .collect() } - pub fn search_results(&self) -> Vec<&DemoThread> { + pub fn search_results(&self) -> Vec<&DemoAtom> { if !self.is_searching() { return Vec::new(); } - self.visible_threads() + self.visible_atoms() } - pub fn settled_visible(&self) -> Vec<&DemoThread> { - self.settled_threads() + pub fn settled_visible(&self) -> Vec<&DemoAtom> { + self.settled_atoms() .into_iter() .take(self.settled_visible_limit) .collect() } pub fn settled_has_more(&self) -> bool { - self.settled_threads().len() > self.settled_visible_limit + self.settled_atoms().len() > self.settled_visible_limit } pub fn show_more_settled(&mut self) { @@ -233,7 +233,7 @@ impl SidebarViewModel { } pub fn pinned_label(&self) -> String { - let count = self.pinned_threads().len(); + let count = self.pinned_atoms().len(); if self.pinned_expanded { "Pinned".to_string() } else { @@ -242,7 +242,7 @@ impl SidebarViewModel { } pub fn settled_label(&self) -> String { - let count = self.settled_threads().len(); + let count = self.settled_atoms().len(); if self.settled_expanded { "Settled".to_string() } else { @@ -251,7 +251,7 @@ impl SidebarViewModel { } pub fn archived_label(&self) -> String { - let count = self.archived_threads().len(); + let count = self.archived_atoms().len(); if self.archived_expanded { "Archived".to_string() } else { @@ -259,56 +259,56 @@ impl SidebarViewModel { } } - pub fn is_active(&self, thread: &DemoThread) -> bool { - thread.id == self.active_thread_id + pub fn is_active(&self, atom: &DemoAtom) -> bool { + atom.id == self.active_atom_id } - pub fn is_selected(&self, thread: &DemoThread) -> bool { - self.selected_thread_ids.contains(thread.id) + pub fn is_selected(&self, atom: &DemoAtom) -> bool { + self.selected_atom_ids.contains(atom.id) } - pub fn is_renaming(&self, thread: &DemoThread) -> bool { - self.renaming_thread_id.as_deref() == Some(thread.id) + pub fn is_renaming(&self, atom: &DemoAtom) -> bool { + self.renaming_atom_id.as_deref() == Some(atom.id) } - pub fn begin_rename(&mut self, thread_id: &str) { - self.renaming_thread_id = Some(thread_id.to_string()); + pub fn begin_rename(&mut self, atom_id: &str) { + self.renaming_atom_id = Some(atom_id.to_string()); } - pub fn commit_rename(&mut self, thread_id: &str, title: String) { + pub fn commit_rename(&mut self, atom_id: &str, title: String) { let trimmed = title.trim(); if !trimmed.is_empty() { self.display_title_overrides - .insert(thread_id.to_string(), trimmed.to_string()); + .insert(atom_id.to_string(), trimmed.to_string()); } - self.renaming_thread_id = None; + self.renaming_atom_id = None; } pub fn cancel_rename(&mut self) { - self.renaming_thread_id = None; + self.renaming_atom_id = None; } - pub fn renaming_title(&self, thread: &DemoThread) -> String { - self.display_title(thread) + pub fn renaming_title(&self, atom: &DemoAtom) -> String { + self.display_title(atom) } - pub fn should_recede(&self, thread: &DemoThread) -> bool { - if self.is_active(thread) || self.is_selected(thread) { + pub fn should_recede(&self, atom: &DemoAtom) -> bool { + if self.is_active(atom) || self.is_selected(atom) { return false; } - if self.is_archived(thread) || self.effective_shelf(thread) == ThreadShelf::Settled { + if self.is_archived(atom) || self.effective_shelf(atom) == AtomShelf::Settled { return true; } - if thread.is_unread || thread.is_woke { + if atom.is_unread || atom.is_woke { return false; } matches!( - thread.status, - ThreadStatus::Ready - | ThreadStatus::Working - | ThreadStatus::Monitoring - | ThreadStatus::Approval - | ThreadStatus::Input + atom.status, + AtomStatus::Ready + | AtomStatus::Working + | AtomStatus::Monitoring + | AtomStatus::Approval + | AtomStatus::Input ) } @@ -318,69 +318,69 @@ impl SidebarViewModel { ids.push(DEMO_DRAFT.id.to_string()); } if self.pinned_expanded { - ids.extend(self.pinned_threads().iter().map(|t| t.id.to_string())); + ids.extend(self.pinned_atoms().iter().map(|t| t.id.to_string())); } - ids.extend(self.active_threads().iter().map(|t| t.id.to_string())); + ids.extend(self.active_atoms().iter().map(|t| t.id.to_string())); if self.settled_expanded { ids.extend(self.settled_visible().iter().map(|t| t.id.to_string())); } ids } - pub fn activate_thread(&mut self, thread_id: &str) { - self.renaming_thread_id = None; - self.active_thread_id = thread_id.to_string(); - self.selected_thread_ids.clear(); - self.selected_thread_ids.insert(thread_id.to_string()); + pub fn activate_atom(&mut self, atom_id: &str) { + self.renaming_atom_id = None; + self.active_atom_id = atom_id.to_string(); + self.selected_atom_ids.clear(); + self.selected_atom_ids.insert(atom_id.to_string()); } - pub fn toggle_thread_selection(&mut self, thread_id: &str, range_select: bool) { + pub fn toggle_atom_selection(&mut self, atom_id: &str, range_select: bool) { if range_select { let ordered = self.ordered_visible_ids(); let anchor = self - .selected_thread_ids + .selected_atom_ids .iter() .next() .cloned() - .or_else(|| Some(self.active_thread_id.clone())); + .or_else(|| Some(self.active_atom_id.clone())); if let Some(anchor_id) = anchor { let from = ordered.iter().position(|id| id == &anchor_id); - let to = ordered.iter().position(|id| id == thread_id); + let to = ordered.iter().position(|id| id == atom_id); if let (Some(from), Some(to)) = (from, to) { let (start, end) = if from <= to { (from, to) } else { (to, from) }; for id in ordered.iter().take(end + 1).skip(start) { - self.selected_thread_ids.insert(id.clone()); + self.selected_atom_ids.insert(id.clone()); } return; } } } - if self.selected_thread_ids.contains(thread_id) { - self.selected_thread_ids.remove(thread_id); + if self.selected_atom_ids.contains(atom_id) { + self.selected_atom_ids.remove(atom_id); } else { - self.selected_thread_ids.insert(thread_id.to_string()); + self.selected_atom_ids.insert(atom_id.to_string()); } } - pub fn can_reorder_threads(&self, dragged_id: &str, target_id: &str) -> bool { + pub fn can_reorder_atoms(&self, dragged_id: &str, target_id: &str) -> bool { if dragged_id == target_id { return false; } - if self.archived_thread_ids.contains(dragged_id) - && self.archived_thread_ids.contains(target_id) + if self.archived_atom_ids.contains(dragged_id) + && self.archived_atom_ids.contains(target_id) { return true; } self.shelf_for_id(dragged_id) == self.shelf_for_id(target_id) } - pub fn reorder_thread(&mut self, dragged_id: &str, target_id: &str, insert_after: bool) { + pub fn reorder_atom(&mut self, dragged_id: &str, target_id: &str, insert_after: bool) { if dragged_id == target_id { return; } - if self.archived_thread_ids.contains(dragged_id) - && self.archived_thread_ids.contains(target_id) + if self.archived_atom_ids.contains(dragged_id) + && self.archived_atom_ids.contains(target_id) { reorder_ids( &mut self.archived_order, @@ -399,9 +399,9 @@ impl SidebarViewModel { reorder_ids(order, dragged_id, target_id, insert_after); } - pub fn can_move_thread(&self, thread_id: &str, delta: isize) -> bool { - let order = self.shelf_order_slice(thread_id); - let pos = order.iter().position(|id| id == thread_id); + pub fn can_move_atom(&self, atom_id: &str, delta: isize) -> bool { + let order = self.shelf_order_slice(atom_id); + let pos = order.iter().position(|id| id == atom_id); match pos { Some(pos) => { let new_pos = pos as isize + delta; @@ -411,9 +411,9 @@ impl SidebarViewModel { } } - pub fn move_thread(&mut self, thread_id: &str, delta: isize) { - if self.archived_thread_ids.contains(thread_id) { - let pos = self.archived_order.iter().position(|id| id == thread_id); + pub fn move_atom(&mut self, atom_id: &str, delta: isize) { + if self.archived_atom_ids.contains(atom_id) { + let pos = self.archived_order.iter().position(|id| id == atom_id); if let Some(pos) = pos { let new_pos = (pos as isize + delta).clamp(0, self.archived_order.len() as isize - 1); @@ -424,9 +424,9 @@ impl SidebarViewModel { } return; } - let shelf = self.shelf_for_id(thread_id); + let shelf = self.shelf_for_id(atom_id); let order = self.shelf_order_mut(shelf); - let pos = order.iter().position(|id| id == thread_id); + let pos = order.iter().position(|id| id == atom_id); if let Some(pos) = pos { let new_pos = (pos as isize + delta).clamp(0, order.len() as isize - 1); if new_pos as usize != pos { @@ -439,14 +439,14 @@ impl SidebarViewModel { pub fn discard_draft(&mut self) { self.draft_visible = false; self.active_order.retain(|id| id != DEMO_DRAFT.id); - if self.active_thread_id == DEMO_DRAFT.id { - self.active_thread_id = self - .active_threads() + if self.active_atom_id == DEMO_DRAFT.id { + self.active_atom_id = self + .active_atoms() .first() .map(|t| t.id.to_string()) .unwrap_or_else(|| "active-1".to_string()); } - self.selected_thread_ids.remove(DEMO_DRAFT.id); + self.selected_atom_ids.remove(DEMO_DRAFT.id); } pub fn show_draft(&mut self) { @@ -456,39 +456,39 @@ impl SidebarViewModel { } } - pub fn activate_from_search(&mut self, thread_id: &str) { - self.prepare_thread_reveal(thread_id); - self.activate_thread(thread_id); + pub fn activate_from_search(&mut self, atom_id: &str) { + self.prepare_atom_reveal(atom_id); + self.activate_atom(atom_id); self.clear_search(); } /// Expands settled pagination when needed; shelf expand animation is handled by the panel. - pub fn prepare_thread_reveal(&mut self, thread_id: &str) { - if self.archived_thread_ids.contains(thread_id) { + pub fn prepare_atom_reveal(&mut self, atom_id: &str) { + if self.archived_atom_ids.contains(atom_id) { return; } - if self.shelf_for_id(thread_id) == ThreadShelf::Settled { - self.reveal_settled_thread(thread_id); + if self.shelf_for_id(atom_id) == AtomShelf::Settled { + self.reveal_settled_atom(atom_id); } } - pub fn reveal_shelf_target(&self, thread_id: &str) -> Option { - if self.archived_thread_ids.contains(thread_id) { + pub fn reveal_shelf_target(&self, atom_id: &str) -> Option { + if self.archived_atom_ids.contains(atom_id) { return Some(RevealShelf::Archived); } - match self.shelf_for_id(thread_id) { - ThreadShelf::Pinned => Some(RevealShelf::Pinned), - ThreadShelf::Settled => Some(RevealShelf::Settled), - ThreadShelf::Active => None, + match self.shelf_for_id(atom_id) { + AtomShelf::Pinned => Some(RevealShelf::Pinned), + AtomShelf::Settled => Some(RevealShelf::Settled), + AtomShelf::Active => None, } } - fn reveal_settled_thread(&mut self, thread_id: &str) { - let settled_len = self.settled_threads().len(); + fn reveal_settled_atom(&mut self, atom_id: &str) { + let settled_len = self.settled_atoms().len(); if let Some(pos) = self - .settled_threads() + .settled_atoms() .iter() - .position(|t| t.id == thread_id) + .position(|t| t.id == atom_id) { let needed = pos + 1; if self.settled_visible_limit < needed { @@ -502,119 +502,119 @@ impl SidebarViewModel { } } - pub fn settle_thread(&mut self, thread_id: &str) { - self.move_thread_to_shelf(thread_id, ThreadShelf::Settled); + pub fn settle_atom(&mut self, atom_id: &str) { + self.move_atom_to_shelf(atom_id, AtomShelf::Settled); } - pub fn unsettle_thread(&mut self, thread_id: &str) { - if self.archived_thread_ids.contains(thread_id) { + pub fn unsettle_atom(&mut self, atom_id: &str) { + if self.archived_atom_ids.contains(atom_id) { return; } - self.move_thread_to_shelf(thread_id, ThreadShelf::Active); + self.move_atom_to_shelf(atom_id, AtomShelf::Active); } - pub fn pin_thread(&mut self, thread_id: &str) { - if self.archived_thread_ids.contains(thread_id) { + pub fn pin_atom(&mut self, atom_id: &str) { + if self.archived_atom_ids.contains(atom_id) { return; } - self.move_thread_to_shelf(thread_id, ThreadShelf::Pinned); + self.move_atom_to_shelf(atom_id, AtomShelf::Pinned); } - pub fn unpin_thread(&mut self, thread_id: &str) { - if self.archived_thread_ids.contains(thread_id) { + pub fn unpin_atom(&mut self, atom_id: &str) { + if self.archived_atom_ids.contains(atom_id) { return; } - self.move_thread_to_shelf(thread_id, ThreadShelf::Active); + self.move_atom_to_shelf(atom_id, AtomShelf::Active); } - pub fn archive_thread(&mut self, thread_id: &str) { - self.remove_from_shelf_orders(thread_id); - self.archived_thread_ids.insert(thread_id.to_string()); - if !self.archived_order.iter().any(|id| id == thread_id) { - self.archived_order.push(thread_id.to_string()); + pub fn archive_atom(&mut self, atom_id: &str) { + self.remove_from_shelf_orders(atom_id); + self.archived_atom_ids.insert(atom_id.to_string()); + if !self.archived_order.iter().any(|id| id == atom_id) { + self.archived_order.push(atom_id.to_string()); } - self.selected_thread_ids.remove(thread_id); - if self.renaming_thread_id.as_deref() == Some(thread_id) { - self.renaming_thread_id = None; + self.selected_atom_ids.remove(atom_id); + if self.renaming_atom_id.as_deref() == Some(atom_id) { + self.renaming_atom_id = None; } - if self.active_thread_id == thread_id { - self.active_thread_id = DEMO_DRAFT.id.to_string(); + if self.active_atom_id == atom_id { + self.active_atom_id = DEMO_DRAFT.id.to_string(); } } - pub fn unarchive_thread(&mut self, thread_id: &str) { - self.archived_thread_ids.remove(thread_id); - self.archived_order.retain(|id| id != thread_id); - if !self.active_order.iter().any(|id| id == thread_id) { - self.active_order.push(thread_id.to_string()); + pub fn unarchive_atom(&mut self, atom_id: &str) { + self.archived_atom_ids.remove(atom_id); + self.archived_order.retain(|id| id != atom_id); + if !self.active_order.iter().any(|id| id == atom_id) { + self.active_order.push(atom_id.to_string()); } } - fn move_thread_to_shelf(&mut self, thread_id: &str, shelf: ThreadShelf) { - self.remove_from_shelf_orders(thread_id); - self.thread_shelf_overrides - .insert(thread_id.to_string(), shelf); - self.append_to_shelf_order(thread_id, shelf); + fn move_atom_to_shelf(&mut self, atom_id: &str, shelf: AtomShelf) { + self.remove_from_shelf_orders(atom_id); + self.atom_shelf_overrides + .insert(atom_id.to_string(), shelf); + self.append_to_shelf_order(atom_id, shelf); } - fn threads_in_order(&self, order: &[String], shelf: ThreadShelf) -> Vec<&DemoThread> { + fn atoms_in_order(&self, order: &[String], shelf: AtomShelf) -> Vec<&DemoAtom> { order .iter() - .filter_map(|id| DEMO_THREADS.iter().find(|t| t.id == id)) + .filter_map(|id| DEMO_ATOMS.iter().find(|t| t.id == id)) .filter(|t| !self.is_archived(t)) .filter(|t| self.effective_shelf(t) == shelf) .filter(|t| self.matches_scope(t)) .collect() } - fn shelf_for_id(&self, thread_id: &str) -> ThreadShelf { - if thread_id == DEMO_DRAFT.id { - return ThreadShelf::Active; + fn shelf_for_id(&self, atom_id: &str) -> AtomShelf { + if atom_id == DEMO_DRAFT.id { + return AtomShelf::Active; } - DEMO_THREADS + DEMO_ATOMS .iter() - .find(|t| t.id == thread_id) + .find(|t| t.id == atom_id) .map(|t| self.effective_shelf(t)) - .unwrap_or(ThreadShelf::Active) + .unwrap_or(AtomShelf::Active) } - fn shelf_order_slice(&self, thread_id: &str) -> &[String] { - if self.archived_thread_ids.contains(thread_id) { + fn shelf_order_slice(&self, atom_id: &str) -> &[String] { + if self.archived_atom_ids.contains(atom_id) { return &self.archived_order; } - match self.shelf_for_id(thread_id) { - ThreadShelf::Pinned => &self.pinned_order, - ThreadShelf::Active => &self.active_order, - ThreadShelf::Settled => &self.settled_order, + match self.shelf_for_id(atom_id) { + AtomShelf::Pinned => &self.pinned_order, + AtomShelf::Active => &self.active_order, + AtomShelf::Settled => &self.settled_order, } } - fn shelf_order_mut(&mut self, shelf: ThreadShelf) -> &mut Vec { + fn shelf_order_mut(&mut self, shelf: AtomShelf) -> &mut Vec { match shelf { - ThreadShelf::Pinned => &mut self.pinned_order, - ThreadShelf::Active => &mut self.active_order, - ThreadShelf::Settled => &mut self.settled_order, + AtomShelf::Pinned => &mut self.pinned_order, + AtomShelf::Active => &mut self.active_order, + AtomShelf::Settled => &mut self.settled_order, } } - fn remove_from_shelf_orders(&mut self, thread_id: &str) { - self.pinned_order.retain(|id| id != thread_id); - self.active_order.retain(|id| id != thread_id); - self.settled_order.retain(|id| id != thread_id); - self.archived_order.retain(|id| id != thread_id); + fn remove_from_shelf_orders(&mut self, atom_id: &str) { + self.pinned_order.retain(|id| id != atom_id); + self.active_order.retain(|id| id != atom_id); + self.settled_order.retain(|id| id != atom_id); + self.archived_order.retain(|id| id != atom_id); } - fn append_to_shelf_order(&mut self, thread_id: &str, shelf: ThreadShelf) { + fn append_to_shelf_order(&mut self, atom_id: &str, shelf: AtomShelf) { let order = self.shelf_order_mut(shelf); - if !order.iter().any(|id| id == thread_id) { - order.push(thread_id.to_string()); + if !order.iter().any(|id| id == atom_id) { + order.push(atom_id.to_string()); } } - fn matches_scope(&self, thread: &DemoThread) -> bool { + fn matches_scope(&self, atom: &DemoAtom) -> bool { match &self.project_scope { None => true, - Some(key) => thread.project_key == key, + Some(key) => atom.project_key == key, } } } @@ -623,8 +623,8 @@ pub fn demo_draft() -> &'static DemoDraft { &DEMO_DRAFT } -fn shelf_order_from_demo(shelf: ThreadShelf) -> Vec { - DEMO_THREADS +fn shelf_order_from_demo(shelf: AtomShelf) -> Vec { + DEMO_ATOMS .iter() .filter(|t| t.shelf == shelf) .map(|t| t.id.to_string()) @@ -664,123 +664,123 @@ mod tests { use super::*; #[test] - fn pin_moves_thread_to_pinned_shelf() { + fn pin_moves_atom_to_pinned_shelf() { let mut view = SidebarViewModel::new("active-1"); - view.pin_thread("active-2"); - assert!(view.pinned_threads().iter().any(|t| t.id == "active-2")); - assert!(!view.active_threads().iter().any(|t| t.id == "active-2")); + view.pin_atom("active-2"); + assert!(view.pinned_atoms().iter().any(|t| t.id == "active-2")); + assert!(!view.active_atoms().iter().any(|t| t.id == "active-2")); } #[test] - fn unpin_moves_thread_back_to_active_list() { + fn unpin_moves_atom_back_to_active_list() { let mut view = SidebarViewModel::new("active-1"); - view.unpin_thread("pinned-1"); - assert!(!view.pinned_threads().iter().any(|t| t.id == "pinned-1")); - assert!(view.active_threads().iter().any(|t| t.id == "pinned-1")); + view.unpin_atom("pinned-1"); + assert!(!view.pinned_atoms().iter().any(|t| t.id == "pinned-1")); + assert!(view.active_atoms().iter().any(|t| t.id == "pinned-1")); } #[test] - fn settle_moves_thread_to_settled_shelf() { + fn settle_moves_atom_to_settled_shelf() { let mut view = SidebarViewModel::new("active-1"); - view.settle_thread("active-2"); - assert!(view.settled_threads().iter().any(|t| t.id == "active-2")); - assert!(!view.active_threads().iter().any(|t| t.id == "active-2")); + view.settle_atom("active-2"); + assert!(view.settled_atoms().iter().any(|t| t.id == "active-2")); + assert!(!view.active_atoms().iter().any(|t| t.id == "active-2")); } #[test] - fn unsettle_moves_thread_back_to_active_list() { + fn unsettle_moves_atom_back_to_active_list() { let mut view = SidebarViewModel::new("active-1"); - view.unsettle_thread("settled-1"); - assert!(!view.settled_threads().iter().any(|t| t.id == "settled-1")); - assert!(view.active_threads().iter().any(|t| t.id == "settled-1")); + view.unsettle_atom("settled-1"); + assert!(!view.settled_atoms().iter().any(|t| t.id == "settled-1")); + assert!(view.active_atoms().iter().any(|t| t.id == "settled-1")); } #[test] - fn settled_threads_always_recede_unless_active_or_selected() { + fn settled_atoms_always_recede_unless_active_or_selected() { let view = SidebarViewModel::new("active-1"); - let failed = DEMO_THREADS.iter().find(|t| t.id == "settled-5").unwrap(); - let woke = DEMO_THREADS.iter().find(|t| t.id == "settled-6").unwrap(); + let failed = DEMO_ATOMS.iter().find(|t| t.id == "settled-5").unwrap(); + let woke = DEMO_ATOMS.iter().find(|t| t.id == "settled-6").unwrap(); assert!(view.should_recede(failed)); assert!(view.should_recede(woke)); } #[test] - fn can_reorder_threads_requires_matching_shelf() { + fn can_reorder_atoms_requires_matching_shelf() { let view = SidebarViewModel::new("active-1"); - assert!(view.can_reorder_threads("active-1", "active-2")); - assert!(!view.can_reorder_threads("active-1", "pinned-1")); - assert!(view.can_reorder_threads("settled-1", "settled-2")); - assert!(!view.can_reorder_threads("settled-1", "active-1")); + assert!(view.can_reorder_atoms("active-1", "active-2")); + assert!(!view.can_reorder_atoms("active-1", "pinned-1")); + assert!(view.can_reorder_atoms("settled-1", "settled-2")); + assert!(!view.can_reorder_atoms("settled-1", "active-1")); } #[test] - fn reorder_settled_moves_thread_within_settled_list() { + fn reorder_settled_moves_atom_within_settled_list() { let mut view = SidebarViewModel::new("active-1"); - view.reorder_thread("settled-1", "settled-2", true); + view.reorder_atom("settled-1", "settled-2", true); let order: Vec<_> = view.settled_order.iter().map(|id| id.as_str()).collect(); assert_eq!(order[0], "settled-2"); assert_eq!(order[1], "settled-1"); } #[test] - fn reorder_pinned_moves_thread_relative_to_target() { + fn reorder_pinned_moves_atom_relative_to_target() { let mut view = SidebarViewModel::new("active-1"); view.pinned_order = vec!["pinned-1".into(), "pinned-2".into()]; - view.reorder_thread("pinned-1", "pinned-2", true); + view.reorder_atom("pinned-1", "pinned-2", true); let order: Vec<_> = view.pinned_order.iter().map(|id| id.as_str()).collect(); assert_eq!(order, ["pinned-2", "pinned-1"]); } #[test] - fn reorder_active_moves_thread_within_active_list() { + fn reorder_active_moves_atom_within_active_list() { let mut view = SidebarViewModel::new("active-1"); view.discard_draft(); - view.reorder_thread("active-1", "active-2", true); + view.reorder_atom("active-1", "active-2", true); let order: Vec<_> = view.active_order.iter().map(|id| id.as_str()).collect(); assert_eq!(order[0], "active-2"); assert_eq!(order[1], "active-1"); } #[test] - fn move_thread_stays_within_settled_shelf_bounds() { + fn move_atom_stays_within_settled_shelf_bounds() { let mut view = SidebarViewModel::new("active-1"); view.settled_order = vec!["settled-1".into(), "settled-2".into()]; - assert!(!view.can_move_thread("settled-1", -1)); - assert!(view.can_move_thread("settled-1", 1)); - view.move_thread("settled-1", -1); + assert!(!view.can_move_atom("settled-1", -1)); + assert!(view.can_move_atom("settled-1", 1)); + view.move_atom("settled-1", -1); let order: Vec<_> = view.settled_order.iter().map(|id| id.as_str()).collect(); assert_eq!(order, ["settled-1", "settled-2"]); - view.move_thread("settled-2", 1); + view.move_atom("settled-2", 1); let order: Vec<_> = view.settled_order.iter().map(|id| id.as_str()).collect(); assert_eq!(order, ["settled-1", "settled-2"]); - view.move_thread("settled-1", 1); + view.move_atom("settled-1", 1); let order: Vec<_> = view.settled_order.iter().map(|id| id.as_str()).collect(); assert_eq!(order, ["settled-2", "settled-1"]); } #[test] - fn archive_moves_thread_to_archived_shelf() { + fn archive_moves_atom_to_archived_shelf() { let mut view = SidebarViewModel::new("active-1"); - view.archive_thread("active-2"); - assert!(view.archived_threads().iter().any(|t| t.id == "active-2")); - assert!(!view.active_threads().iter().any(|t| t.id == "active-2")); + view.archive_atom("active-2"); + assert!(view.archived_atoms().iter().any(|t| t.id == "active-2")); + assert!(!view.active_atoms().iter().any(|t| t.id == "active-2")); } #[test] - fn unarchive_restores_thread_to_active_list() { + fn unarchive_restores_atom_to_active_list() { let mut view = SidebarViewModel::new("active-1"); - view.archive_thread("active-2"); - view.unarchive_thread("active-2"); - assert!(view.archived_threads().is_empty()); - assert!(view.active_threads().iter().any(|t| t.id == "active-2")); + view.archive_atom("active-2"); + view.unarchive_atom("active-2"); + assert!(view.archived_atoms().is_empty()); + assert!(view.active_atoms().iter().any(|t| t.id == "active-2")); } #[test] - fn archive_removes_thread_from_lists() { + fn archive_removes_atom_from_lists() { let mut view = SidebarViewModel::new("active-1"); - view.archive_thread("active-2"); - assert!(!view.active_threads().iter().any(|t| t.id == "active-2")); - assert!(view.visible_threads().iter().all(|t| t.id != "active-2")); + view.archive_atom("active-2"); + assert!(!view.active_atoms().iter().any(|t| t.id == "active-2")); + assert!(view.visible_atoms().iter().all(|t| t.id != "active-2")); } #[test] @@ -790,7 +790,7 @@ mod tests { view.settled_expanded = false; view.settled_visible_limit = 1; view.activate_from_search("settled-1"); - assert_eq!(view.active_thread_id, "settled-1"); + assert_eq!(view.active_atom_id, "settled-1"); assert!(view.search_query.is_empty()); assert_eq!( view.reveal_shelf_target("settled-1"), @@ -804,12 +804,12 @@ mod tests { fn commit_rename_updates_display_title() { let mut view = SidebarViewModel::new("active-1"); view.begin_rename("active-1"); - view.commit_rename("active-1", "Renamed thread".to_string()); + view.commit_rename("active-1", "Renamed atom".to_string()); assert_eq!( - view.display_title(DEMO_THREADS.iter().find(|t| t.id == "active-1").unwrap()), - "Renamed thread" + view.display_title(DEMO_ATOMS.iter().find(|t| t.id == "active-1").unwrap()), + "Renamed atom" ); - assert!(view.renaming_thread_id.is_none()); + assert!(view.renaming_atom_id.is_none()); } #[test] @@ -817,6 +817,6 @@ mod tests { let mut view = SidebarViewModel::new("active-1"); view.begin_rename("active-1"); view.cancel_rename(); - assert!(view.renaming_thread_id.is_none()); + assert!(view.renaming_atom_id.is_none()); } } diff --git a/src/app/shell/left_sidebar/surfaces.rs b/src/app/shell/left_sidebar/surfaces.rs index 46f7a59..c3b19ad 100644 --- a/src/app/shell/left_sidebar/surfaces.rs +++ b/src/app/shell/left_sidebar/surfaces.rs @@ -1,4 +1,4 @@ -//! Row surface colors for sidebar thread rows. +//! Row surface colors for sidebar atom rows. use gpui::Hsla; @@ -6,7 +6,7 @@ use crate::shared::theme::{ BackgroundToken, ForegroundToken, OpenCoreTheme, SUCCESS_GREEN, WARNING_AMBER, }; -use super::demo_data::ThreadStatus; +use super::demo_data::AtomStatus; pub fn row_hover_bg(theme: &OpenCoreTheme) -> Hsla { theme.surface(BackgroundToken::Secondary).alpha(0.55) @@ -34,15 +34,15 @@ pub fn draft_bg_hover(_theme: &OpenCoreTheme) -> Hsla { color.alpha(0.12) } -pub fn status_color(status: ThreadStatus, theme: &OpenCoreTheme, dimmed: bool) -> Hsla { +pub fn status_color(status: AtomStatus, theme: &OpenCoreTheme, dimmed: bool) -> Hsla { let hsla = match status { - ThreadStatus::Working | ThreadStatus::Monitoring => gpui::rgb(0x0E_A5_E9).into(), - ThreadStatus::Approval | ThreadStatus::Woke => gpui::rgb(WARNING_AMBER).into(), - ThreadStatus::Input => gpui::rgb(0x63_66_F1).into(), - ThreadStatus::Failed => theme.foreground(ForegroundToken::Accent), - ThreadStatus::Ready => theme.foreground(ForegroundToken::Muted), + AtomStatus::Working | AtomStatus::Monitoring => gpui::rgb(0x0E_A5_E9).into(), + AtomStatus::Approval | AtomStatus::Woke => gpui::rgb(WARNING_AMBER).into(), + AtomStatus::Input => gpui::rgb(0x63_66_F1).into(), + AtomStatus::Failed => theme.foreground(ForegroundToken::Accent), + AtomStatus::Ready => theme.foreground(ForegroundToken::Muted), }; - if dimmed && matches!(status, ThreadStatus::Working | ThreadStatus::Monitoring) { + if dimmed && matches!(status, AtomStatus::Working | AtomStatus::Monitoring) { hsla.alpha(0.75) } else { hsla From b06422106f4a8badb0a5dbc64d9c9366176049dc Mon Sep 17 00:00:00 2001 From: Bambang Tri Rahmat Doni Date: Wed, 2 Sep 2026 15:18:10 +0700 Subject: [PATCH 2/7] refactor(shell): rename thread sidebar UI components to atom Rename thread_row to atom_row and update panel, row, drag, and scope UI to use atom naming, labels, and element ids throughout the left dock. --- .../content/{thread_row.rs => atom_row.rs} | 224 +++++++++--------- .../shell/left_sidebar/content/draft_row.rs | 38 +-- .../shell/left_sidebar/content/empty_state.rs | 2 +- src/app/shell/left_sidebar/content/mod.rs | 6 +- .../left_sidebar/content/project_scope_row.rs | 4 +- .../left_sidebar/content/search_result_row.rs | 20 +- .../shell/left_sidebar/content/search_row.rs | 10 +- .../shell/left_sidebar/content/shelf_body.rs | 2 +- .../left_sidebar/content/shelf_header.rs | 2 +- src/app/shell/left_sidebar/mod.rs | 2 +- src/app/shell/left_sidebar/panel.rs | 222 ++++++++--------- src/app/shell/left_sidebar/shelf_tween.rs | 2 +- src/app/shell/left_sidebar/tokens.rs | 6 +- src/app/shell/workspace.rs | 2 +- 14 files changed, 271 insertions(+), 271 deletions(-) rename src/app/shell/left_sidebar/content/{thread_row.rs => atom_row.rs} (79%) diff --git a/src/app/shell/left_sidebar/content/thread_row.rs b/src/app/shell/left_sidebar/content/atom_row.rs similarity index 79% rename from src/app/shell/left_sidebar/content/thread_row.rs rename to src/app/shell/left_sidebar/content/atom_row.rs index 8a35247..895a47c 100644 --- a/src/app/shell/left_sidebar/content/thread_row.rs +++ b/src/app/shell/left_sidebar/content/atom_row.rs @@ -1,4 +1,4 @@ -//! Thread row surfaces: card layout for inbox/pinned, slim layout for history. +//! atom row surfaces: card layout for inbox/pinned, slim layout for history. use gpui::{ AnyElement, App, AppContext, ClickEvent, Context, Entity, InteractiveElement, IntoElement, @@ -14,7 +14,7 @@ use gpui_component::{ use crate::shared::theme::{BackgroundToken, ForegroundToken, OpenCoreTheme, TypeRole}; -use super::super::demo_data::{DEMO_PROJECTS, DemoThread, ThreadShelf, ThreadStatus}; +use super::super::demo_data::{DEMO_PROJECTS, DemoAtom, AtomShelf, AtomStatus}; use super::super::state::SidebarViewModel; use super::super::surfaces::{ drop_line_color, pr_open_color, project_favicon_color, row_active_bg, row_hover_bg, @@ -24,48 +24,48 @@ use super::super::tokens::{ FAVICON_SIZE, ROW_CONTENT_INSET, ROW_HEIGHT_CARD, ROW_HEIGHT_SLIM, ROW_RADIUS, }; use super::callbacks::{ - ThreadDragOverCallback, ThreadDropCallback, ThreadHoverCallback, ThreadIdCallback, - ThreadMoveCallback, ThreadSelectCallback, + AtomDragOverCallback, AtomDropCallback, AtomHoverCallback, AtomIdCallback, + AtomMoveCallback, AtomSelectCallback, }; -use super::pinned_drag::{PinnedRowDragUi, PinnedThreadDrag, ThreadDragScope}; +use super::pinned_drag::{PinnedRowDragUi, PinnedAtomDrag, AtomDragScope}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ThreadRowVariant { +pub enum AtomRowVariant { Card, Slim, } #[derive(Clone)] -pub struct ThreadRowActions { - pub on_activate: ThreadIdCallback, - pub on_select: ThreadSelectCallback, - pub on_hover: ThreadHoverCallback, - pub on_move_thread: ThreadMoveCallback, - pub on_pin: ThreadIdCallback, - pub on_unpin: ThreadIdCallback, - pub on_settle: ThreadIdCallback, - pub on_unsettle: ThreadIdCallback, - pub on_rename: ThreadIdCallback, - pub on_archive: ThreadIdCallback, - pub on_unarchive: ThreadIdCallback, - pub on_pinned_drag_start: ThreadIdCallback, - pub on_pinned_drag_over: ThreadDragOverCallback, - pub on_pinned_drop: ThreadDropCallback, +pub struct AtomRowActions { + pub on_activate: AtomIdCallback, + pub on_select: AtomSelectCallback, + pub on_hover: AtomHoverCallback, + pub on_move_atom: AtomMoveCallback, + pub on_pin: AtomIdCallback, + pub on_unpin: AtomIdCallback, + pub on_settle: AtomIdCallback, + pub on_unsettle: AtomIdCallback, + pub on_rename: AtomIdCallback, + pub on_archive: AtomIdCallback, + pub on_unarchive: AtomIdCallback, + pub on_pinned_drag_start: AtomIdCallback, + pub on_pinned_drag_over: AtomDragOverCallback, + pub on_pinned_drop: AtomDropCallback, } -pub fn sidebar_thread_row( - thread: &DemoThread, - variant: ThreadRowVariant, +pub fn sidebar_atom_row( + atom: &DemoAtom, + variant: AtomRowVariant, view: &SidebarViewModel, theme: &OpenCoreTheme, - actions: &ThreadRowActions, + actions: &AtomRowActions, rename_input: Option<&Entity>, pinned_drag: Option, scroll_anchor: Option, ) -> AnyElement { match variant { - ThreadRowVariant::Card => card_row( - thread, + AtomRowVariant::Card => card_row( + atom, view, theme, actions, @@ -74,8 +74,8 @@ pub fn sidebar_thread_row( scroll_anchor, ) .into_any_element(), - ThreadRowVariant::Slim => slim_row( - thread, + AtomRowVariant::Slim => slim_row( + atom, view, theme, actions, @@ -109,19 +109,19 @@ pub fn sidebar_show_more_button( } fn row_surface( - thread: &DemoThread, + atom: &DemoAtom, view: &SidebarViewModel, theme: &OpenCoreTheme, height: f32, variant_key: &str, - actions: &ThreadRowActions, + actions: &AtomRowActions, pinned_drag: Option, scroll_anchor: Option, content: impl IntoElement, ) -> impl IntoElement { - let is_active = view.is_active(thread); - let is_selected = view.is_selected(thread); - let recede = view.should_recede(thread); + let is_active = view.is_active(atom); + let is_selected = view.is_selected(atom); + let recede = view.should_recede(atom); let (bg, text) = if is_active { ( @@ -144,26 +144,26 @@ fn row_surface( ) }; - let thread_id = thread.id.to_string(); + let atom_id = atom.id.to_string(); let on_select = actions.on_select.clone(); let on_activate = actions.on_activate.clone(); let on_hover = actions.on_hover.clone(); let on_pinned_drag_start = actions.on_pinned_drag_start.clone(); let on_pinned_drag_over = actions.on_pinned_drag_over.clone(); let on_pinned_drop = actions.on_pinned_drop.clone(); - let is_pinned = view.effective_shelf(thread) == ThreadShelf::Pinned; - let is_settled = view.effective_shelf(thread) == ThreadShelf::Settled; - let is_archived = view.is_archived(thread); + let is_pinned = view.effective_shelf(atom) == AtomShelf::Pinned; + let is_settled = view.effective_shelf(atom) == AtomShelf::Settled; + let is_archived = view.is_archived(atom); let drag_scope = - ThreadDragScope::from_thread(thread.id, view.effective_shelf(thread), is_archived); - let menu_title = view.display_title(thread); + AtomDragScope::from_atom(atom.id, view.effective_shelf(atom), is_archived); + let menu_title = view.display_title(atom); let row_drag = pinned_drag.unwrap_or_default(); let drag_hover_bg = row_hover_bg(theme); let is_card = variant_key == "card"; div() - .id(format!("left-sidebar-thread-{variant_key}-{}", thread.id)) + .id(format!("left-sidebar-atom-{variant_key}-{}", atom.id)) .anchor_scroll(scroll_anchor) .w_full() .min_w_0() @@ -208,8 +208,8 @@ fn row_surface( }) .when(row_drag.is_source, |row| row.opacity(0.45)) .on_drag( - PinnedThreadDrag { - thread_id: thread_id.clone(), + PinnedAtomDrag { + atom_id: atom_id.clone(), scope: drag_scope, title: menu_title.clone().into(), preview_bg: theme.surface(BackgroundToken::Secondary), @@ -219,7 +219,7 @@ fn row_surface( let on_pinned_drag_start = on_pinned_drag_start.clone(); move |drag, _, window, cx| { cx.stop_propagation(); - on_pinned_drag_start(drag.thread_id.clone(), window, cx); + on_pinned_drag_start(drag.atom_id.clone(), window, cx); cx.new(|_| PinnedDragPreview { title: drag.title.clone(), bg: drag.preview_bg, @@ -229,33 +229,33 @@ fn row_surface( }, ) .can_drop({ - let self_id = thread_id.clone(); + let self_id = atom_id.clone(); let self_scope = drag_scope; move |value, _, _| { value - .downcast_ref::() + .downcast_ref::() .is_some_and(|drag| { - drag.thread_id != self_id && drag.scope.allows_drop(self_scope) + drag.atom_id != self_id && drag.scope.allows_drop(self_scope) }) } }) - .drag_over::({ - let self_id = thread_id.clone(); + .drag_over::({ + let self_id = atom_id.clone(); let self_scope = drag_scope; move |style, drag, _, _| { - if drag.thread_id == self_id || !drag.scope.allows_drop(self_scope) { + if drag.atom_id == self_id || !drag.scope.allows_drop(self_scope) { return style; } style.bg(drag_hover_bg) } }) - .on_drag_move::({ - let target_id = thread_id.clone(); + .on_drag_move::({ + let target_id = atom_id.clone(); let self_scope = drag_scope; let on_pinned_drag_over = on_pinned_drag_over.clone(); move |event, window, cx| { let drag = event.drag(cx); - if drag.thread_id == target_id || !drag.scope.allows_drop(self_scope) { + if drag.atom_id == target_id || !drag.scope.allows_drop(self_scope) { return; } let pointer = event.event.position; @@ -274,19 +274,19 @@ fn row_surface( } }) .on_drop({ - let target_id = thread_id.clone(); + let target_id = atom_id.clone(); let self_scope = drag_scope; let on_pinned_drop = on_pinned_drop.clone(); - move |drag: &PinnedThreadDrag, window, cx| { - if drag.thread_id == target_id || !drag.scope.allows_drop(self_scope) { + move |drag: &PinnedAtomDrag, window, cx| { + if drag.atom_id == target_id || !drag.scope.allows_drop(self_scope) { return; } - on_pinned_drop(drag.thread_id.clone(), target_id.clone(), window, cx); + on_pinned_drop(drag.atom_id.clone(), target_id.clone(), window, cx); } }) .on_hover({ let on_hover = on_hover.clone(); - let hover_id = thread_id.clone(); + let hover_id = atom_id.clone(); move |hovered, window, cx| { on_hover( if *hovered { @@ -302,7 +302,7 @@ fn row_surface( .on_click({ let on_select = on_select.clone(); let on_activate = on_activate.clone(); - let click_id = thread_id.clone(); + let click_id = atom_id.clone(); move |event: &ClickEvent, window, cx| { if event.click_count() >= 2 { return; @@ -316,17 +316,17 @@ fn row_surface( }) .context_menu({ let actions = actions.clone(); - let menu_id = thread_id.clone(); + let menu_id = atom_id.clone(); let menu_title = menu_title.clone(); - let menu_state = ThreadContextMenuState { + let menu_state = AtomContextMenuState { is_pinned, is_settled, is_archived, - can_move_up: view.can_move_thread(&thread_id, -1), - can_move_down: view.can_move_thread(&thread_id, 1), + can_move_up: view.can_move_atom(&atom_id, -1), + can_move_down: view.can_move_atom(&atom_id, 1), }; move |menu, window, cx| { - build_thread_context_menu( + build_atom_context_menu( menu, menu_id.clone(), menu_title.clone(), @@ -341,7 +341,7 @@ fn row_surface( } #[derive(Clone, Copy)] -struct ThreadContextMenuState { +struct AtomContextMenuState { is_pinned: bool, is_settled: bool, is_archived: bool, @@ -349,16 +349,16 @@ struct ThreadContextMenuState { can_move_down: bool, } -fn build_thread_context_menu( +fn build_atom_context_menu( mut menu: gpui_component::menu::PopupMenu, menu_id: String, menu_title: String, - state: ThreadContextMenuState, - actions: &ThreadRowActions, + state: AtomContextMenuState, + actions: &AtomRowActions, _window: &mut Window, _cx: &mut Context, ) -> gpui_component::menu::PopupMenu { - let ThreadContextMenuState { + let AtomContextMenuState { is_pinned, is_settled, is_archived, @@ -369,7 +369,7 @@ fn build_thread_context_menu( if can_move_up { let up_id = menu_id.clone(); - let on_move_up = actions.on_move_thread.clone(); + let on_move_up = actions.on_move_atom.clone(); menu = menu.item( PopupMenuItem::new("Move up").on_click(move |_, window, cx| { on_move_up(up_id.clone(), -1, window, cx); @@ -378,7 +378,7 @@ fn build_thread_context_menu( } if can_move_down { let down_id = menu_id.clone(); - let on_move_down = actions.on_move_thread.clone(); + let on_move_down = actions.on_move_atom.clone(); menu = menu.item( PopupMenuItem::new("Move down").on_click(move |_, window, cx| { on_move_down(down_id.clone(), 1, window, cx); @@ -449,26 +449,26 @@ fn build_thread_context_menu( } fn card_row( - thread: &DemoThread, + atom: &DemoAtom, view: &SidebarViewModel, theme: &OpenCoreTheme, - actions: &ThreadRowActions, + actions: &AtomRowActions, rename_input: Option<&Entity>, pinned_drag: Option, scroll_anchor: Option, ) -> impl IntoElement { let secondary = theme.foreground(ForegroundToken::Secondary); let mono = mono_family(); - let recede = view.should_recede(thread); - let title = view.display_title(thread); + let recede = view.should_recede(atom); + let title = view.display_title(atom); let favicon_hue = DEMO_PROJECTS .iter() - .find(|p| p.key == thread.project_key) + .find(|p| p.key == atom.project_key) .map(|p| p.favicon_hue) .unwrap_or(0x888888); row_surface( - thread, + atom, view, theme, ROW_HEIGHT_CARD, @@ -507,12 +507,12 @@ fn card_row( } else { secondary }) - .child(thread.project_title), + .child(atom.project_title), ) - .child(status_or_time(thread, view, theme, true)), + .child(status_or_time(atom, view, theme, true)), ) .child(title_row( - thread, + atom, view, &title, rename_input, @@ -523,26 +523,26 @@ fn card_row( theme.foreground(ForegroundToken::Primary).alpha(0.9) }, )) - .child(branch_meta_row(thread, theme)), + .child(branch_meta_row(atom, theme)), ), ) } fn slim_row( - thread: &DemoThread, + atom: &DemoAtom, view: &SidebarViewModel, theme: &OpenCoreTheme, - actions: &ThreadRowActions, + actions: &AtomRowActions, rename_input: Option<&Entity>, pinned_drag: Option, scroll_anchor: Option, ) -> impl IntoElement { let muted = theme.foreground(ForegroundToken::Muted); - let recede = view.should_recede(thread); - let title = view.display_title(thread); + let recede = view.should_recede(atom); + let title = view.display_title(atom); row_surface( - thread, + atom, view, theme, ROW_HEIGHT_SLIM, @@ -565,7 +565,7 @@ fn slim_row( .flex_shrink_0(), ) .child(title_row( - thread, + atom, view, &title, rename_input, @@ -576,12 +576,12 @@ fn slim_row( theme.foreground(ForegroundToken::Primary) }, )) - .child(status_or_time(thread, view, theme, false)), + .child(status_or_time(atom, view, theme, false)), ) } fn title_row( - thread: &DemoThread, + atom: &DemoAtom, view: &SidebarViewModel, title: &str, rename_input: Option<&Entity>, @@ -590,7 +590,7 @@ fn title_row( ) -> gpui::AnyElement { let mono = mono_family(); let title = title.to_string(); - if view.is_renaming(thread) + if view.is_renaming(atom) && let Some(rename_input) = rename_input { return div() @@ -635,22 +635,22 @@ fn project_favicon(hue: u32) -> impl IntoElement { } fn status_or_time( - thread: &DemoThread, + atom: &DemoAtom, view: &SidebarViewModel, theme: &OpenCoreTheme, show_status: bool, ) -> impl IntoElement { let muted = theme.foreground(ForegroundToken::Muted); let mono = mono_family(); - let dimmed = view.should_recede(thread) && !view.is_active(thread); + let dimmed = view.should_recede(atom) && !view.is_active(atom); - if show_status && let Some(label) = thread.status.label() { - let color = status_color(thread.status, theme, dimmed); + if show_status && let Some(label) = atom.status.label() { + let color = status_color(atom.status, theme, dimmed); return h_flex() .flex_shrink_0() .gap(px(4.)) .items_center() - .children(status_icon(thread.status, color)) + .children(status_icon(atom.status, color)) .child( div() .font_family(mono) @@ -666,20 +666,20 @@ fn status_or_time( .font_family(mono) .text_size(px(TypeRole::LabelMd.size())) .text_color(muted) - .child(thread.time_label) + .child(atom.time_label) .into_any_element() } -fn status_icon(status: ThreadStatus, color: gpui::Hsla) -> Option { +fn status_icon(status: AtomStatus, color: gpui::Hsla) -> Option { match status { - ThreadStatus::Working => Some( + AtomStatus::Working => Some( Icon::new(IconName::LoaderCircle) .text_color(color) .small() .flex_shrink_0() .into_any_element(), ), - ThreadStatus::Woke => Some( + AtomStatus::Woke => Some( Icon::new(IconName::Bell) .text_color(color) .small() @@ -690,7 +690,7 @@ fn status_icon(status: ThreadStatus, color: gpui::Hsla) -> Option impl IntoElement { +fn branch_meta_row(atom: &DemoAtom, theme: &OpenCoreTheme) -> impl IntoElement { let muted = theme.foreground(ForegroundToken::Muted); let green = pr_open_color(); let red = theme.foreground(ForegroundToken::Accent); @@ -703,7 +703,7 @@ fn branch_meta_row(thread: &DemoThread, theme: &OpenCoreTheme) -> impl IntoEleme .gap(px(6.)) .overflow_hidden() .text_size(px(TypeRole::LabelMd.size())) - .child(thread.branch.map_or_else( + .child(atom.branch.map_or_else( || div().flex_1().min_w_0().into_any_element(), |branch| { div() @@ -717,13 +717,13 @@ fn branch_meta_row(thread: &DemoThread, theme: &OpenCoreTheme) -> impl IntoEleme .into_any_element() }, )) - .children(terminal_indicator(thread, muted)) - .children(pr_badge(thread, muted, mono.clone())) - .children(diff_stats(thread, green, red, mono)) + .children(terminal_indicator(atom, muted)) + .children(pr_badge(atom, muted, mono.clone())) + .children(diff_stats(atom, green, red, mono)) } -fn terminal_indicator(thread: &DemoThread, muted: gpui::Hsla) -> Vec { - if thread.terminal_process_count == 0 { +fn terminal_indicator(atom: &DemoAtom, muted: gpui::Hsla) -> Vec { + if atom.terminal_process_count == 0 { return Vec::new(); } vec![ @@ -735,8 +735,8 @@ fn terminal_indicator(thread: &DemoThread, muted: gpui::Hsla) -> Vec Vec { - thread +fn pr_badge(atom: &DemoAtom, color: gpui::Hsla, mono: SharedString) -> Vec { + atom .pr_number .map(|number| { div() @@ -751,17 +751,17 @@ fn pr_badge(thread: &DemoThread, color: gpui::Hsla, mono: SharedString) -> Vec Vec { - if thread.diff_insertions.is_none() && thread.diff_deletions.is_none() { + if atom.diff_insertions.is_none() && atom.diff_deletions.is_none() { return Vec::new(); } let mut elements = Vec::new(); - if let Some(n) = thread.diff_insertions { + if let Some(n) = atom.diff_insertions { elements.push( div() .flex_shrink_0() @@ -771,7 +771,7 @@ fn diff_stats( .into_any_element(), ); } - if let Some(n) = thread.diff_deletions { + if let Some(n) = atom.diff_deletions { elements.push( div() .flex_shrink_0() diff --git a/src/app/shell/left_sidebar/content/draft_row.rs b/src/app/shell/left_sidebar/content/draft_row.rs index d154f46..aa51fd8 100644 --- a/src/app/shell/left_sidebar/content/draft_row.rs +++ b/src/app/shell/left_sidebar/content/draft_row.rs @@ -1,4 +1,4 @@ -//! Draft row for unsent composer sessions — matches active card row interaction. +//! Draft row for unsent composer content in a new atom — matches active card row interaction. use gpui::{ App, AppContext, ClickEvent, InteractiveElement, IntoElement, ParentElement, SharedString, @@ -18,13 +18,13 @@ use super::super::surfaces::{ row_selected_bg, }; use super::super::tokens::{FAVICON_SIZE, ROW_CONTENT_INSET, ROW_HEIGHT_CARD}; -use super::callbacks::{ThreadDragOverCallback, ThreadDropCallback, ThreadIdCallback}; -use super::pinned_drag::{PinnedRowDragUi, PinnedThreadDrag, ThreadDragScope}; +use super::callbacks::{AtomDragOverCallback, AtomDropCallback, AtomIdCallback}; +use super::pinned_drag::{PinnedRowDragUi, PinnedAtomDrag, AtomDragScope}; pub struct DraftRowDragActions { - pub on_drag_start: ThreadIdCallback, - pub on_drag_over: ThreadDragOverCallback, - pub on_drop: ThreadDropCallback, + pub on_drag_start: AtomIdCallback, + pub on_drag_over: AtomDragOverCallback, + pub on_drop: AtomDropCallback, } pub fn sidebar_draft_row( @@ -63,7 +63,7 @@ pub fn sidebar_draft_row( let on_drag_start = drag_actions.on_drag_start.clone(); let on_drag_over = drag_actions.on_drag_over.clone(); let on_drop = drag_actions.on_drop.clone(); - let drag_scope = ThreadDragScope::Active; + let drag_scope = AtomDragScope::Active; div() .id(format!("left-sidebar-draft-{}", draft.id)) @@ -104,8 +104,8 @@ pub fn sidebar_draft_row( }) .when(row_drag.is_source, |row| row.opacity(0.45)) .on_drag( - PinnedThreadDrag { - thread_id: draft_id.clone(), + PinnedAtomDrag { + atom_id: draft_id.clone(), scope: drag_scope, title: preview.into(), preview_bg: theme.surface(BackgroundToken::Secondary), @@ -115,7 +115,7 @@ pub fn sidebar_draft_row( let on_drag_start = on_drag_start.clone(); move |drag, _, window, cx| { cx.stop_propagation(); - on_drag_start(drag.thread_id.clone(), window, cx); + on_drag_start(drag.atom_id.clone(), window, cx); cx.new(|_| DraftDragPreview { title: drag.title.clone(), bg: drag.preview_bg, @@ -128,27 +128,27 @@ pub fn sidebar_draft_row( let self_id = draft_id.clone(); move |value, _, _| { value - .downcast_ref::() + .downcast_ref::() .is_some_and(|drag| { - drag.thread_id != self_id && drag.scope.allows_drop(drag_scope) + drag.atom_id != self_id && drag.scope.allows_drop(drag_scope) }) } }) - .drag_over::({ + .drag_over::({ let self_id = draft_id.clone(); move |style, drag, _, _| { - if drag.thread_id == self_id || !drag.scope.allows_drop(drag_scope) { + if drag.atom_id == self_id || !drag.scope.allows_drop(drag_scope) { return style; } style.bg(drag_hover_bg) } }) - .on_drag_move::({ + .on_drag_move::({ let target_id = draft_id.clone(); let on_drag_over = on_drag_over.clone(); move |event, window, cx| { let drag = event.drag(cx); - if drag.thread_id == target_id || !drag.scope.allows_drop(drag_scope) { + if drag.atom_id == target_id || !drag.scope.allows_drop(drag_scope) { return; } let pointer = event.event.position; @@ -169,11 +169,11 @@ pub fn sidebar_draft_row( .on_drop({ let target_id = draft_id.clone(); let on_drop = on_drop.clone(); - move |drag: &PinnedThreadDrag, window, cx| { - if drag.thread_id == target_id || !drag.scope.allows_drop(drag_scope) { + move |drag: &PinnedAtomDrag, window, cx| { + if drag.atom_id == target_id || !drag.scope.allows_drop(drag_scope) { return; } - on_drop(drag.thread_id.clone(), target_id.clone(), window, cx); + on_drop(drag.atom_id.clone(), target_id.clone(), window, cx); } }) .on_click({ diff --git a/src/app/shell/left_sidebar/content/empty_state.rs b/src/app/shell/left_sidebar/content/empty_state.rs index 289340a..cb6e8e4 100644 --- a/src/app/shell/left_sidebar/content/empty_state.rs +++ b/src/app/shell/left_sidebar/content/empty_state.rs @@ -1,4 +1,4 @@ -//! Empty states for zero-project and zero-thread shelves. +//! Empty states for zero-project and zero-atom shelves. use gpui::{IntoElement, ParentElement, SharedString, Styled, div, px, relative}; use gpui_component::{ diff --git a/src/app/shell/left_sidebar/content/mod.rs b/src/app/shell/left_sidebar/content/mod.rs index d45973a..4176e51 100644 --- a/src/app/shell/left_sidebar/content/mod.rs +++ b/src/app/shell/left_sidebar/content/mod.rs @@ -7,7 +7,7 @@ mod search_result_row; mod search_row; mod shelf_body; mod shelf_header; -mod thread_row; +mod atom_row; pub use draft_row::{DraftRowDragActions, sidebar_draft_row}; pub use empty_state::{sidebar_add_project_button, sidebar_empty_state}; @@ -17,6 +17,6 @@ pub use search_result_row::sidebar_search_result_row; pub use search_row::sidebar_search_row; pub use shelf_body::sidebar_shelf_body; pub use shelf_header::{ShelfTone, sidebar_section_header, sidebar_shelf_header}; -pub use thread_row::{ - ThreadRowActions, ThreadRowVariant, sidebar_show_more_button, sidebar_thread_row, +pub use atom_row::{ + AtomRowActions, AtomRowVariant, sidebar_show_more_button, sidebar_atom_row, }; diff --git a/src/app/shell/left_sidebar/content/project_scope_row.rs b/src/app/shell/left_sidebar/content/project_scope_row.rs index ffce0ca..4cfffb8 100644 --- a/src/app/shell/left_sidebar/content/project_scope_row.rs +++ b/src/app/shell/left_sidebar/content/project_scope_row.rs @@ -15,7 +15,7 @@ use crate::shared::theme::{ BackgroundToken, BorderToken, ForegroundToken, OpenCoreTheme, SpacingToken, TypeRole, }; -use super::super::demo_data::{ALL_PROJECTS_LABEL, DEMO_PROJECTS}; +use super::super::demo_data::{ALL_ATOMS_LABEL, DEMO_PROJECTS}; use super::super::tokens::ICON_BUTTON_SIZE; pub fn sidebar_project_scope_row( @@ -65,7 +65,7 @@ pub fn sidebar_project_scope_row( move |menu, _window, _cx| { let mut menu = menu.label("Projects"); let on_all = on_scope.clone(); - menu = menu.item(PopupMenuItem::new(ALL_PROJECTS_LABEL).on_click( + menu = menu.item(PopupMenuItem::new(ALL_ATOMS_LABEL).on_click( move |_, window, cx| { on_all(None, window, cx); }, diff --git a/src/app/shell/left_sidebar/content/search_result_row.rs b/src/app/shell/left_sidebar/content/search_result_row.rs index 4930271..ec5f1db 100644 --- a/src/app/shell/left_sidebar/content/search_result_row.rs +++ b/src/app/shell/left_sidebar/content/search_result_row.rs @@ -8,26 +8,26 @@ use gpui_component::{h_flex, v_flex}; use crate::shared::theme::{ForegroundToken, OpenCoreTheme, TypeRole}; -use super::super::demo_data::{DEMO_PROJECTS, DemoThread}; +use super::super::demo_data::{DEMO_PROJECTS, DemoAtom}; use super::super::state::SidebarViewModel; use super::super::surfaces::{project_favicon_color, row_active_bg, row_hover_bg}; use super::super::tokens::{FAVICON_SIZE, ROW_CONTENT_INSET, ROW_HEIGHT_SLIM, ROW_RADIUS}; pub fn sidebar_search_result_row( - thread: &DemoThread, + atom: &DemoAtom, view: &SidebarViewModel, theme: &OpenCoreTheme, on_activate: impl Fn(String, &mut Window, &mut App) + 'static, ) -> impl IntoElement { - let is_active = view.is_active(thread); - let title = view.display_title(thread); + let is_active = view.is_active(atom); + let title = view.display_title(atom); let primary = theme.foreground(ForegroundToken::Primary); let secondary = theme.foreground(ForegroundToken::Secondary); let muted = theme.foreground(ForegroundToken::Muted); let mono = mono_family(); let favicon_hue = DEMO_PROJECTS .iter() - .find(|p| p.key == thread.project_key) + .find(|p| p.key == atom.project_key) .map(|p| p.favicon_hue) .unwrap_or(0x888888); @@ -37,10 +37,10 @@ pub fn sidebar_search_result_row( theme.surface(crate::shared::theme::BackgroundToken::Primary) }; - let thread_id = thread.id.to_string(); + let atom_id = atom.id.to_string(); div() - .id(format!("left-sidebar-search-{}", thread.id)) + .id(format!("left-sidebar-search-{}", atom.id)) .w_full() .min_w_0() .h(px(ROW_HEIGHT_SLIM)) @@ -50,7 +50,7 @@ pub fn sidebar_search_result_row( .bg(bg) .cursor_pointer() .hover(|style| style.bg(row_hover_bg(theme))) - .on_click(move |_: &ClickEvent, window, cx| on_activate(thread_id.clone(), window, cx)) + .on_click(move |_: &ClickEvent, window, cx| on_activate(atom_id.clone(), window, cx)) .child( h_flex() .w_full() @@ -83,7 +83,7 @@ pub fn sidebar_search_result_row( .font_family(mono) .text_size(px(TypeRole::MonoSm.size())) .text_color(secondary.alpha(0.8)) - .child(thread.project_title), + .child(atom.project_title), ), ) .child( @@ -92,7 +92,7 @@ pub fn sidebar_search_result_row( .font_family(mono_family()) .text_size(px(TypeRole::LabelMd.size())) .text_color(muted) - .child(thread.time_label), + .child(atom.time_label), ), ) } diff --git a/src/app/shell/left_sidebar/content/search_row.rs b/src/app/shell/left_sidebar/content/search_row.rs index a28e823..1c8baef 100644 --- a/src/app/shell/left_sidebar/content/search_row.rs +++ b/src/app/shell/left_sidebar/content/search_row.rs @@ -1,4 +1,4 @@ -//! Search row with clear affordance and new-thread action. +//! Search row with clear affordance and new-atom action. use gpui::{App, Entity, InteractiveElement, IntoElement, ParentElement, Styled, Window, div, px}; use gpui_component::{ @@ -19,7 +19,7 @@ pub fn sidebar_search_row( query: &str, theme: &OpenCoreTheme, on_clear: impl Fn(&mut Window, &mut App) + 'static, - on_new_thread: impl Fn(&mut Window, &mut App) + 'static, + on_new_atom: impl Fn(&mut Window, &mut App) + 'static, ) -> impl IntoElement { let surface = theme.surface(BackgroundToken::Secondary); let border = theme.border_token(BorderToken::Default); @@ -78,10 +78,10 @@ pub fn sidebar_search_row( }), ) .child( - Button::new("left-sidebar-new-thread") + Button::new("left-sidebar-new-atom") .ghost() .rounded(ButtonRounded::None) - .tooltip("New thread") + .tooltip("New atom") .icon(Icon::new(IconName::File).text_color(primary)) .h(px(ICON_BUTTON_SIZE)) .w(px(ICON_BUTTON_SIZE)) @@ -89,6 +89,6 @@ pub fn sidebar_search_row( .border_1() .border_color(border) .bg(surface) - .on_click(move |_, window, cx| on_new_thread(window, cx)), + .on_click(move |_, window, cx| on_new_atom(window, cx)), ) } diff --git a/src/app/shell/left_sidebar/content/shelf_body.rs b/src/app/shell/left_sidebar/content/shelf_body.rs index 2c491d8..e43fff8 100644 --- a/src/app/shell/left_sidebar/content/shelf_body.rs +++ b/src/app/shell/left_sidebar/content/shelf_body.rs @@ -1,4 +1,4 @@ -//! Animated clip wrapper for collapsible shelf thread rows. +//! Animated clip wrapper for collapsible shelf atom rows. use gpui::{InteractiveElement, IntoElement, ParentElement, Styled, div, px}; use gpui_component::v_flex; diff --git a/src/app/shell/left_sidebar/content/shelf_header.rs b/src/app/shell/left_sidebar/content/shelf_header.rs index c3feb72..eb8a92a 100644 --- a/src/app/shell/left_sidebar/content/shelf_header.rs +++ b/src/app/shell/left_sidebar/content/shelf_header.rs @@ -1,4 +1,4 @@ -//! Collapsible shelf headers and static section labels for thread list shelves. +//! Collapsible shelf headers and static section labels for atom list shelves. use gpui::{ App, InteractiveElement, IntoElement, MouseButton, ParentElement, Radians, SharedString, diff --git a/src/app/shell/left_sidebar/mod.rs b/src/app/shell/left_sidebar/mod.rs index e2fd9be..1426af6 100644 --- a/src/app/shell/left_sidebar/mod.rs +++ b/src/app/shell/left_sidebar/mod.rs @@ -1,4 +1,4 @@ -//! Thread sidebar — interface components for the left dock. +//! atom sidebar — interface components for the left dock. #![allow(clippy::too_many_arguments)] diff --git a/src/app/shell/left_sidebar/panel.rs b/src/app/shell/left_sidebar/panel.rs index 3053998..c1a365a 100644 --- a/src/app/shell/left_sidebar/panel.rs +++ b/src/app/shell/left_sidebar/panel.rs @@ -1,4 +1,4 @@ -//! Left dock panel — thread sidebar (demo data). +//! Left dock panel — atom sidebar (demo data). use std::collections::HashMap; use std::rc::Rc; @@ -20,13 +20,13 @@ use crate::shared::theme::{BackgroundToken, BorderToken, OpenCoreTheme, SpacingT use super::chrome::sidebar_chrome_footer; use super::content::{ - DraftRowDragActions, PinnedDragState, PinnedRowDragUi, ShelfTone, ThreadRowActions, - ThreadRowVariant, sidebar_add_project_button, sidebar_draft_row, sidebar_empty_state, + DraftRowDragActions, PinnedDragState, PinnedRowDragUi, ShelfTone, AtomRowActions, + AtomRowVariant, sidebar_add_project_button, sidebar_draft_row, sidebar_empty_state, sidebar_project_scope_row, sidebar_search_result_row, sidebar_search_row, sidebar_section_header, sidebar_shelf_body, sidebar_shelf_header, sidebar_show_more_button, - sidebar_thread_row, + sidebar_atom_row, }; -use super::demo_data::{DEMO_DRAFT, DEMO_THREADS}; +use super::demo_data::{DEMO_DRAFT, DEMO_ATOMS}; use super::shelf_tween::{ ShelfHeightTween, eval_shelf_tween, shelf_content_height_card, shelf_content_height_slim, shelf_expand_progress, @@ -34,7 +34,7 @@ use super::shelf_tween::{ use super::state::{FooterBackContext, RevealShelf, SidebarViewModel, demo_draft}; use super::tokens::{CONTENT_INSET, DOCK_RESIZE_GUTTER}; -const PANEL_TITLE: &str = "THREADS"; +const PANEL_TITLE: &str = "ATOMS"; pub struct LeftSidebarPanel { focus_handle: FocusHandle, @@ -42,8 +42,8 @@ pub struct LeftSidebarPanel { search: Entity, rename_input: Entity, view: SidebarViewModel, - thread_list_scroll_handle: ScrollHandle, - thread_scroll_anchors: HashMap, + atom_list_scroll_handle: ScrollHandle, + atom_scroll_anchors: HashMap, pinned_height_tween: Option, settled_height_tween: Option, archived_height_tween: Option, @@ -58,7 +58,7 @@ pub struct LeftSidebarPanel { impl LeftSidebarPanel { pub fn new(window: &mut Window, theme: WorkspaceTheme, cx: &mut Context) -> Self { let search = cx.new(|cx| InputState::new(window, cx).placeholder("Search")); - let rename_input = cx.new(|cx| InputState::new(window, cx).placeholder("Thread name")); + let rename_input = cx.new(|cx| InputState::new(window, cx).placeholder("Atom name")); let _panel = cx.entity(); let search_subscription = cx.subscribe_in(&search, window, move |this, _, event, _, cx| { if matches!(event, InputEvent::Change) { @@ -88,8 +88,8 @@ impl LeftSidebarPanel { search, rename_input, view: SidebarViewModel::new("active-1"), - thread_list_scroll_handle: ScrollHandle::new(), - thread_scroll_anchors: HashMap::new(), + atom_list_scroll_handle: ScrollHandle::new(), + atom_scroll_anchors: HashMap::new(), pinned_height_tween: None, settled_height_tween: None, archived_height_tween: None, @@ -155,7 +155,7 @@ impl LeftSidebarPanel { if !self.pending_scroll_to_bottom { return; } - self.thread_list_scroll_handle.scroll_to_bottom(); + self.atom_list_scroll_handle.scroll_to_bottom(); if !self.shelf_expand_tween_active(now) { self.pending_scroll_to_bottom = false; self.defer_finalize_scroll_to_bottom(window, cx); @@ -166,19 +166,19 @@ impl LeftSidebarPanel { let panel = cx.entity(); window.defer(cx, move |window, cx| { panel.update(cx, |panel, cx| { - let max = panel.thread_list_scroll_handle.max_offset(); + let max = panel.atom_list_scroll_handle.max_offset(); panel - .thread_list_scroll_handle + .atom_list_scroll_handle .set_offset(gpui::Point::new(px(0.), -max.y)); - panel.thread_list_scroll_handle.scroll_to_bottom(); + panel.atom_list_scroll_handle.scroll_to_bottom(); cx.notify(); }); let panel = panel.clone(); window.defer(cx, move |_, cx| { panel.update(cx, |panel, cx| { - let max = panel.thread_list_scroll_handle.max_offset(); + let max = panel.atom_list_scroll_handle.max_offset(); panel - .thread_list_scroll_handle + .atom_list_scroll_handle .set_offset(gpui::Point::new(px(0.), -max.y)); cx.notify(); }); @@ -260,22 +260,22 @@ impl LeftSidebarPanel { fn activate_from_search_animated( &mut self, - thread_id: &str, + atom_id: &str, now: Instant, window: &mut Window, cx: &mut Context, ) { - let shelf = self.view.reveal_shelf_target(thread_id); + let shelf = self.view.reveal_shelf_target(atom_id); if let Some(shelf) = shelf { self.expand_reveal_shelf(shelf, now, cx); } - self.view.activate_from_search(thread_id); + self.view.activate_from_search(atom_id); self.clear_search_input(window, cx); cx.notify(); } fn pinned_shelf_full_height(&self) -> f32 { - shelf_content_height_card(self.view.pinned_threads().len()) + shelf_content_height_card(self.view.pinned_atoms().len()) } fn toggle_pinned_shelf(&mut self, now: Instant, cx: &mut Context) { @@ -300,7 +300,7 @@ impl LeftSidebarPanel { } fn archived_shelf_full_height(&self) -> f32 { - shelf_content_height_slim(self.view.archived_threads().len(), false) + shelf_content_height_slim(self.view.archived_atoms().len(), false) } fn toggle_settled_shelf(&mut self, now: Instant, cx: &mut Context) { @@ -355,14 +355,14 @@ impl LeftSidebarPanel { } } - fn thread_actions(&self, cx: &mut Context) -> ThreadRowActions { + fn atom_actions(&self, cx: &mut Context) -> AtomRowActions { let panel = cx.entity(); - ThreadRowActions { + AtomRowActions { on_activate: Rc::new({ let panel = panel.clone(); move |id: String, _window, cx| { panel.update(cx, |panel, cx| { - panel.view.activate_thread(&id); + panel.view.activate_atom(&id); cx.notify(); }); } @@ -372,9 +372,9 @@ impl LeftSidebarPanel { move |id: String, range, _window, cx| { panel.update(cx, |panel, cx| { if range { - panel.view.toggle_thread_selection(&id, true); + panel.view.toggle_atom_selection(&id, true); } else { - panel.view.toggle_thread_selection(&id, false); + panel.view.toggle_atom_selection(&id, false); } cx.notify(); }); @@ -384,16 +384,16 @@ impl LeftSidebarPanel { let panel = panel.clone(); move |id: Option, _window, cx| { panel.update(cx, |panel, cx| { - panel.view.hovered_thread_id = id; + panel.view.hovered_atom_id = id; cx.notify(); }); } }), - on_move_thread: Rc::new({ + on_move_atom: Rc::new({ let panel = panel.clone(); move |id: String, delta, _window, cx| { panel.update(cx, |panel, cx| { - panel.view.move_thread(&id, delta); + panel.view.move_atom(&id, delta); cx.notify(); }); } @@ -403,7 +403,7 @@ impl LeftSidebarPanel { move |id: String, _window, cx| { panel.update(cx, |panel, cx| { let now = Instant::now(); - panel.view.pin_thread(&id); + panel.view.pin_atom(&id); panel.expand_reveal_shelf(RevealShelf::Pinned, now, cx); }); } @@ -412,7 +412,7 @@ impl LeftSidebarPanel { let panel = panel.clone(); move |id: String, _window, cx| { panel.update(cx, |panel, cx| { - panel.view.unpin_thread(&id); + panel.view.unpin_atom(&id); cx.notify(); }); } @@ -422,7 +422,7 @@ impl LeftSidebarPanel { move |id: String, _window, cx| { panel.update(cx, |panel, cx| { let now = Instant::now(); - panel.view.settle_thread(&id); + panel.view.settle_atom(&id); panel.expand_reveal_shelf(RevealShelf::Settled, now, cx); }); } @@ -431,7 +431,7 @@ impl LeftSidebarPanel { let panel = panel.clone(); move |id: String, _window, cx| { panel.update(cx, |panel, cx| { - panel.view.unsettle_thread(&id); + panel.view.unsettle_atom(&id); cx.notify(); }); } @@ -440,7 +440,7 @@ impl LeftSidebarPanel { let panel = panel.clone(); move |id: String, window, cx| { panel.update(cx, |panel, cx| { - panel.begin_rename_thread(&id, window, cx); + panel.begin_rename_atom(&id, window, cx); }); } }), @@ -449,7 +449,7 @@ impl LeftSidebarPanel { move |id: String, _window, cx| { panel.update(cx, |panel, cx| { let now = Instant::now(); - panel.view.archive_thread(&id); + panel.view.archive_atom(&id); panel.expand_reveal_shelf(RevealShelf::Archived, now, cx); }); } @@ -458,7 +458,7 @@ impl LeftSidebarPanel { let panel = panel.clone(); move |id: String, _window, cx| { panel.update(cx, |panel, cx| { - panel.view.unarchive_thread(&id); + panel.view.unarchive_atom(&id); cx.notify(); }); } @@ -479,7 +479,7 @@ impl LeftSidebarPanel { panel.update(cx, |panel, cx| { let dragged_id = panel.pinned_dragging_id.clone(); if let Some(dragged_id) = dragged_id - && !panel.view.can_reorder_threads(&dragged_id, &target_id) + && !panel.view.can_reorder_atoms(&dragged_id, &target_id) { if panel.pinned_drop_target.is_some() { panel.pinned_drop_target = None; @@ -500,7 +500,7 @@ impl LeftSidebarPanel { let panel = panel.clone(); move |dragged_id: String, target_id: String, _window, cx| { panel.update(cx, |panel, cx| { - if !panel.view.can_reorder_threads(&dragged_id, &target_id) { + if !panel.view.can_reorder_atoms(&dragged_id, &target_id) { panel.pinned_dragging_id = None; panel.pinned_drop_target = None; cx.notify(); @@ -514,7 +514,7 @@ impl LeftSidebarPanel { .unwrap_or(false); panel .view - .reorder_thread(&dragged_id, &target_id, insert_after); + .reorder_atom(&dragged_id, &target_id, insert_after); panel.pinned_dragging_id = None; panel.pinned_drop_target = None; cx.notify(); @@ -531,45 +531,45 @@ impl LeftSidebarPanel { self.view.clear_search(); } - fn thread_scroll_anchor( + fn atom_scroll_anchor( anchors: &mut HashMap, scroll_handle: &ScrollHandle, - thread_id: &str, + atom_id: &str, ) -> ScrollAnchor { anchors - .entry(thread_id.to_string()) + .entry(atom_id.to_string()) .or_insert_with(|| ScrollAnchor::for_handle(scroll_handle.clone())) .clone() } - fn scroll_to_thread(&self, thread_id: &str, window: &mut Window, _cx: &mut App) { - if let Some(anchor) = self.thread_scroll_anchors.get(thread_id) { + fn scroll_to_atom(&self, atom_id: &str, window: &mut Window, _cx: &mut App) { + if let Some(anchor) = self.atom_scroll_anchors.get(atom_id) { anchor.scroll_to(window, _cx); } } - fn begin_rename_thread( + fn begin_rename_atom( &mut self, - thread_id: &str, + atom_id: &str, window: &mut Window, cx: &mut Context, ) { - let thread = super::demo_data::DEMO_THREADS + let atom = super::demo_data::DEMO_ATOMS .iter() - .find(|t| t.id == thread_id); - if thread.is_none() { + .find(|t| t.id == atom_id); + if atom.is_none() { return; } - let title = self.view.renaming_title(thread.unwrap()); - self.view.begin_rename(thread_id); + let title = self.view.renaming_title(atom.unwrap()); + self.view.begin_rename(atom_id); self.rename_input.update(cx, |input, cx| { input.set_value(title, window, cx); }); let panel = cx.entity(); - let thread_id = thread_id.to_string(); + let atom_id = atom_id.to_string(); window.defer(cx, move |window, cx| { panel.update(cx, |panel, cx| { - if panel.view.renaming_thread_id.as_deref() != Some(&thread_id) { + if panel.view.renaming_atom_id.as_deref() != Some(&atom_id) { return; } panel.rename_input.update(cx, |input, cx| { @@ -581,9 +581,9 @@ impl LeftSidebarPanel { } fn commit_rename(&mut self, window: &mut Window, cx: &mut Context) { - if let Some(thread_id) = self.view.renaming_thread_id.clone() { + if let Some(atom_id) = self.view.renaming_atom_id.clone() { let title = self.rename_input.read(cx).value().to_string(); - self.view.commit_rename(&thread_id, title); + self.view.commit_rename(&atom_id, title); cx.notify(); let _ = window; } @@ -641,10 +641,10 @@ impl Render for LeftSidebarPanel { let border = theme.border_token(BorderToken::Default); let view = &self.view; let panel = cx.entity(); - let actions = self.thread_actions(cx); + let actions = self.atom_actions(cx); let rename_input = &self.rename_input; let drag_state = self.pinned_drag_state(); - let thread_list_el = thread_list( + let atom_list_el = atom_list( self.pinned_height_tween, self.settled_height_tween, self.archived_height_tween, @@ -655,8 +655,8 @@ impl Render for LeftSidebarPanel { rename_input, panel.clone(), now, - &self.thread_list_scroll_handle, - &mut self.thread_scroll_anchors, + &self.atom_list_scroll_handle, + &mut self.atom_scroll_anchors, window, cx, ); @@ -685,8 +685,8 @@ impl Render for LeftSidebarPanel { .min_w_0() .overflow_y_scroll() .overflow_x_hidden() - .track_scroll(&self.thread_list_scroll_handle) - .child(thread_list_el), + .track_scroll(&self.atom_list_scroll_handle) + .child(atom_list_el), ) .child(sidebar_chrome_footer( view.footer_mode, @@ -806,41 +806,41 @@ fn sidebar_fixed_controls( )) } -fn thread_row_with_scroll( - thread: &super::demo_data::DemoThread, - variant: ThreadRowVariant, +fn atom_row_with_scroll( + atom: &super::demo_data::DemoAtom, + variant: AtomRowVariant, view: &SidebarViewModel, theme: &OpenCoreTheme, - actions: &ThreadRowActions, + actions: &AtomRowActions, rename_input: &Entity, row_drag: Option, scroll_handle: &ScrollHandle, scroll_anchors: &mut HashMap, ) -> gpui::AnyElement { - sidebar_thread_row( - thread, + sidebar_atom_row( + atom, variant, view, theme, actions, Some(rename_input), row_drag, - Some(LeftSidebarPanel::thread_scroll_anchor( + Some(LeftSidebarPanel::atom_scroll_anchor( scroll_anchors, scroll_handle, - thread.id, + atom.id, )), ) } -fn thread_list( +fn atom_list( pinned_height_tween: Option, settled_height_tween: Option, archived_height_tween: Option, drag_state: PinnedDragState, view: &SidebarViewModel, theme: &OpenCoreTheme, - actions: &ThreadRowActions, + actions: &AtomRowActions, rename_input: &Entity, panel_entity: Entity, now: Instant, @@ -853,13 +853,13 @@ fn thread_list( return search_results_list(view, theme, panel_entity).into_any_element(); } - let pinned_threads = view.pinned_threads(); - let pinned_count = pinned_threads.len(); + let pinned_atoms = view.pinned_atoms(); + let pinned_count = pinned_atoms.len(); let settled_visible = view.settled_visible(); - let archived_threads = view.archived_threads(); - let settled_count = view.settled_threads().len(); + let archived_atoms = view.archived_atoms(); + let settled_count = view.settled_atoms().len(); let settled_visible_count = settled_visible.len(); - let archived_count = archived_threads.len(); + let archived_count = archived_atoms.len(); let settled_show_more = view.settled_expanded && view.settled_has_more(); let pinned_full_height = shelf_content_height_card(pinned_count); let settled_full_height = shelf_content_height_slim(settled_visible_count, settled_show_more); @@ -915,7 +915,7 @@ fn thread_list( let archived_label = view.archived_label(); v_flex() - .id("left-sidebar-thread-list") + .id("left-sidebar-atom-list") .w_full() .min_w_0() .gap(px(1.)) @@ -944,13 +944,13 @@ fn thread_list( "pinned", pinned_clip, pinned_show, - pinned_threads + pinned_atoms .iter() - .map(|thread| { - let row_drag = Some(drag_state.for_thread(thread.id)); - thread_row_with_scroll( - thread, - ThreadRowVariant::Card, + .map(|atom| { + let row_drag = Some(drag_state.for_atom(atom.id)); + atom_row_with_scroll( + atom, + AtomRowVariant::Card, view, theme, actions, @@ -978,7 +978,7 @@ fn thread_list( .children(if show_empty { Some( v_flex() - .child(sidebar_empty_state("No threads yet", theme)) + .child(sidebar_empty_state("No atoms yet", theme)) .child(sidebar_add_project_button(theme, { let panel = panel_entity.clone(); move |_window, cx| { @@ -1017,11 +1017,11 @@ fn thread_list( settled_show, settled_visible .iter() - .map(|thread| { - let row_drag = Some(drag_state.for_thread(thread.id)); - thread_row_with_scroll( - thread, - ThreadRowVariant::Slim, + .map(|atom| { + let row_drag = Some(drag_state.for_atom(atom.id)); + atom_row_with_scroll( + atom, + AtomRowVariant::Slim, view, theme, actions, @@ -1068,13 +1068,13 @@ fn thread_list( "archived", archived_clip, archived_show, - archived_threads + archived_atoms .iter() - .map(|thread| { - let row_drag = Some(drag_state.for_thread(thread.id)); - thread_row_with_scroll( - thread, - ThreadRowVariant::Slim, + .map(|atom| { + let row_drag = Some(drag_state.for_atom(atom.id)); + atom_row_with_scroll( + atom, + AtomRowVariant::Slim, view, theme, actions, @@ -1106,16 +1106,16 @@ fn search_results_list( .pr(px(CONTENT_INSET + DOCK_RESIZE_GUTTER)) .pb(px(CONTENT_INSET)) .children(if results.is_empty() { - Some(sidebar_empty_state("No threads found", theme)) + Some(sidebar_empty_state("No atoms found", theme)) } else { None }) .children( results .iter() - .map(|thread| { + .map(|atom| { let panel = panel_for_activate.clone(); - sidebar_search_result_row(thread, view, theme, move |id, window, cx| { + sidebar_search_result_row(atom, view, theme, move |id, window, cx| { let panel_defer = panel.clone(); let scroll_id = id.clone(); panel.update(cx, |panel, cx| { @@ -1123,7 +1123,7 @@ fn search_results_list( }); window.defer(cx, move |window, cx| { panel_defer.update(cx, |panel, cx| { - panel.scroll_to_thread(&scroll_id, window, cx); + panel.scroll_to_atom(&scroll_id, window, cx); cx.notify(); }); }); @@ -1136,7 +1136,7 @@ fn search_results_list( fn active_section_rows( view: &SidebarViewModel, theme: &OpenCoreTheme, - actions: &ThreadRowActions, + actions: &AtomRowActions, draft_drag: &DraftRowDragActions, drag_state: &PinnedDragState, rename_input: &Entity, @@ -1148,7 +1148,7 @@ fn active_section_rows( .iter() .map(|id| { if id == DEMO_DRAFT.id { - let row_drag = drag_state.for_thread(DEMO_DRAFT.id); + let row_drag = drag_state.for_atom(DEMO_DRAFT.id); sidebar_draft_row( demo_draft(), view.is_draft_active(), @@ -1160,7 +1160,7 @@ fn active_section_rows( let panel = panel_entity.clone(); move |_window, cx| { panel.update(cx, |panel, cx| { - panel.view.activate_thread(DEMO_DRAFT.id); + panel.view.activate_atom(DEMO_DRAFT.id); cx.notify(); }); } @@ -1170,9 +1170,9 @@ fn active_section_rows( move |range, _window, cx| { panel.update(cx, |panel, cx| { if range { - panel.view.toggle_thread_selection(DEMO_DRAFT.id, true); + panel.view.toggle_atom_selection(DEMO_DRAFT.id, true); } else { - panel.view.toggle_thread_selection(DEMO_DRAFT.id, false); + panel.view.toggle_atom_selection(DEMO_DRAFT.id, false); } cx.notify(); }); @@ -1190,14 +1190,14 @@ fn active_section_rows( ) .into_any_element() } else { - let thread = DEMO_THREADS + let atom = DEMO_ATOMS .iter() .find(|t| t.id == id) - .expect("active section id must map to demo thread"); - let row_drag = Some(drag_state.for_thread(thread.id)); - thread_row_with_scroll( - thread, - ThreadRowVariant::Card, + .expect("active section id must map to demo atom"); + let row_drag = Some(drag_state.for_atom(atom.id)); + atom_row_with_scroll( + atom, + AtomRowVariant::Card, view, theme, actions, diff --git a/src/app/shell/left_sidebar/shelf_tween.rs b/src/app/shell/left_sidebar/shelf_tween.rs index a246f95..f97c589 100644 --- a/src/app/shell/left_sidebar/shelf_tween.rs +++ b/src/app/shell/left_sidebar/shelf_tween.rs @@ -1,4 +1,4 @@ -//! Manual height tweens for collapsible thread shelves (pinned / settled / archived). +//! Manual height tweens for collapsible atom shelves (pinned / settled / archived). //! //! GPUI has no CSS transitions. Clip height is eased over 200ms with the same //! resize ease-out as dock panel toggles, evaluated each render. diff --git a/src/app/shell/left_sidebar/tokens.rs b/src/app/shell/left_sidebar/tokens.rs index 7962a00..becc2ba 100644 --- a/src/app/shell/left_sidebar/tokens.rs +++ b/src/app/shell/left_sidebar/tokens.rs @@ -1,11 +1,11 @@ -//! Layout tokens for the left sidebar thread list. +//! Layout tokens for the left sidebar atom list. pub const CONTENT_INSET: f32 = 8.0; -/// Keeps thread metadata clear of the dock resize handle at the panel edge. +/// Keeps atom metadata clear of the dock resize handle at the panel edge. pub const DOCK_RESIZE_GUTTER: f32 = 6.0; pub const ROW_CONTENT_INSET: f32 = 10.0; pub const ROW_HEIGHT_SLIM: f32 = 36.0; -/// Vertical gap between slim shelf rows (matches thread list `gap(1.)`). +/// Vertical gap between slim shelf rows (matches atom list `gap(1.)`). pub const SHELF_ROW_GAP: f32 = 1.0; /// Approximate height of the settled "Show more" control (`py(8)` + label). pub const SHOW_MORE_HEIGHT: f32 = 32.0; diff --git a/src/app/shell/workspace.rs b/src/app/shell/workspace.rs index f886a99..b15f357 100644 --- a/src/app/shell/workspace.rs +++ b/src/app/shell/workspace.rs @@ -393,7 +393,7 @@ mod tests { ); assert!( dock_item_panel_count(panel) >= 1, - "left dock expected threads-only panel" + "left dock expected atoms-only panel" ); } From e97321e1610e236ca2cf327d49504f431714413e Mon Sep 17 00:00:00 2001 From: Bambang Tri Rahmat Doni Date: Wed, 2 Sep 2026 15:18:16 +0700 Subject: [PATCH 3/7] refactor(shell): align workspace copy with atom terminology Replace session-oriented empty-state and composer hints with atom wording so the center workspace reflects that chat history belongs to each atom. --- src/app/shell/main_workspace_panel.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/shell/main_workspace_panel.rs b/src/app/shell/main_workspace_panel.rs index f0944b7..7785b93 100644 --- a/src/app/shell/main_workspace_panel.rs +++ b/src/app/shell/main_workspace_panel.rs @@ -21,7 +21,7 @@ use crate::shared::theme::{ use super::workspace_theme::WorkspaceTheme; const PANEL_TITLE: &str = "WORKSPACE"; -const EMPTY_HEADLINE: &str = "Start a new session"; +const EMPTY_HEADLINE: &str = "Start a new atom"; const EMPTY_BODY: &str = "Ask questions, run commands, and edit files without leaving your machine."; const COMPOSER_PLACEHOLDER: &str = "Ask anything…"; @@ -206,7 +206,7 @@ fn quick_actions_row( border: gpui::Hsla, primary: gpui::Hsla, ) -> impl IntoElement { - let actions = ["New session", "Run command", "Open file"]; + let actions = ["New atom", "Run command", "Open file"]; h_flex() .gap(px(SpacingToken::S1.value())) .flex_wrap() @@ -295,7 +295,7 @@ fn composer_bar( .font_family(mono) .text_size(px(TypeRole::MonoSm.size())) .text_color(muted) - .child("Enter to send · Shift+Enter for newline · ⌘K for commands"), + .child("Enter to send atom · Shift+Enter for newline · ⌘K for commands"), ) } From 23cb198b5b7f123c9382b7ca72b8447363226603 Mon Sep 17 00:00:00 2001 From: Bambang Tri Rahmat Doni Date: Wed, 2 Sep 2026 15:56:11 +0700 Subject: [PATCH 4/7] refactor(shell): polish atom terminology after code review Align scope picker copy, scoped label lookup, and remaining docs with atom naming. --- src/app/shell/left_sidebar/content/atom_row.rs | 2 +- src/app/shell/left_sidebar/content/project_scope_row.rs | 2 +- src/app/shell/left_sidebar/demo_data.rs | 2 +- src/app/shell/left_sidebar/mod.rs | 2 +- src/app/shell/left_sidebar/state.rs | 9 +++++---- src/app/shell/workspace_theme.rs | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/app/shell/left_sidebar/content/atom_row.rs b/src/app/shell/left_sidebar/content/atom_row.rs index 895a47c..1cf3e68 100644 --- a/src/app/shell/left_sidebar/content/atom_row.rs +++ b/src/app/shell/left_sidebar/content/atom_row.rs @@ -1,4 +1,4 @@ -//! atom row surfaces: card layout for inbox/pinned, slim layout for history. +//! Atom row surfaces: card layout for inbox/pinned, slim layout for history. use gpui::{ AnyElement, App, AppContext, ClickEvent, Context, Entity, InteractiveElement, IntoElement, diff --git a/src/app/shell/left_sidebar/content/project_scope_row.rs b/src/app/shell/left_sidebar/content/project_scope_row.rs index 4cfffb8..1324f01 100644 --- a/src/app/shell/left_sidebar/content/project_scope_row.rs +++ b/src/app/shell/left_sidebar/content/project_scope_row.rs @@ -63,7 +63,7 @@ pub fn sidebar_project_scope_row( .dropdown_menu({ let on_scope = on_scope.clone(); move |menu, _window, _cx| { - let mut menu = menu.label("Projects"); + let mut menu = menu.label("Scope"); let on_all = on_scope.clone(); menu = menu.item(PopupMenuItem::new(ALL_ATOMS_LABEL).on_click( move |_, window, cx| { diff --git a/src/app/shell/left_sidebar/demo_data.rs b/src/app/shell/left_sidebar/demo_data.rs index a609e4d..66b2294 100644 --- a/src/app/shell/left_sidebar/demo_data.rs +++ b/src/app/shell/left_sidebar/demo_data.rs @@ -266,7 +266,7 @@ pub const DEMO_ATOMS: [DemoAtom; 15] = [ }, DemoAtom { id: "settled-6", - title: "atom sidebar search", + title: "Atom sidebar search", project_key: "sample_app", project_title: "sample_app", branch: Some("feat/search"), diff --git a/src/app/shell/left_sidebar/mod.rs b/src/app/shell/left_sidebar/mod.rs index 1426af6..b45596d 100644 --- a/src/app/shell/left_sidebar/mod.rs +++ b/src/app/shell/left_sidebar/mod.rs @@ -1,4 +1,4 @@ -//! atom sidebar — interface components for the left dock. +//! Atom sidebar — interface components for the left dock. #![allow(clippy::too_many_arguments)] diff --git a/src/app/shell/left_sidebar/state.rs b/src/app/shell/left_sidebar/state.rs index cde0b9f..ebd823b 100644 --- a/src/app/shell/left_sidebar/state.rs +++ b/src/app/shell/left_sidebar/state.rs @@ -3,7 +3,8 @@ use std::collections::{HashMap, HashSet}; use super::demo_data::{ - ALL_ATOMS_LABEL, DEMO_DRAFT, DEMO_ATOMS, DemoDraft, DemoAtom, AtomShelf, AtomStatus, + ALL_ATOMS_LABEL, DEMO_DRAFT, DEMO_ATOMS, DEMO_PROJECTS, DemoDraft, DemoAtom, AtomShelf, + AtomStatus, }; pub const SETTLED_PAGE_INITIAL: usize = 10; @@ -127,10 +128,10 @@ impl SidebarViewModel { pub fn scoped_label(&self) -> &str { match &self.project_scope { None => ALL_ATOMS_LABEL, - Some(key) => DEMO_ATOMS + Some(key) => DEMO_PROJECTS .iter() - .find(|t| t.project_key == key) - .map(|t| t.project_title) + .find(|project| project.key == key) + .map(|project| project.display_name) .unwrap_or(ALL_ATOMS_LABEL), } } diff --git a/src/app/shell/workspace_theme.rs b/src/app/shell/workspace_theme.rs index 296dd44..4c1dc69 100644 --- a/src/app/shell/workspace_theme.rs +++ b/src/app/shell/workspace_theme.rs @@ -40,7 +40,7 @@ pub fn install_workspace_theme(theme: WorkspaceTheme) -> WorkspaceTheme { }) } -/// Returns the installed workspace theme for the current thread. +/// Returns the installed workspace theme for the current atom. pub fn workspace_theme() -> WorkspaceTheme { WORKSPACE_THEME.with(|theme| theme.clone()) } From 7a7cd3d67266898b71e1b07b91bf35f902ffbbc6 Mon Sep 17 00:00:00 2001 From: Bambang Tri Rahmat Doni Date: Wed, 2 Sep 2026 15:59:10 +0700 Subject: [PATCH 5/7] style(shell): apply rustfmt to left sidebar atom rename Fix cargo fmt check failures in CI for the atom terminology refactor. --- .../shell/left_sidebar/content/atom_row.rs | 22 ++++++++----------- .../shell/left_sidebar/content/draft_row.rs | 10 ++++----- src/app/shell/left_sidebar/content/mod.rs | 6 ++--- src/app/shell/left_sidebar/panel.rs | 16 +++++--------- src/app/shell/left_sidebar/state.rs | 19 +++++----------- 5 files changed, 26 insertions(+), 47 deletions(-) diff --git a/src/app/shell/left_sidebar/content/atom_row.rs b/src/app/shell/left_sidebar/content/atom_row.rs index 1cf3e68..1869bb2 100644 --- a/src/app/shell/left_sidebar/content/atom_row.rs +++ b/src/app/shell/left_sidebar/content/atom_row.rs @@ -14,7 +14,7 @@ use gpui_component::{ use crate::shared::theme::{BackgroundToken, ForegroundToken, OpenCoreTheme, TypeRole}; -use super::super::demo_data::{DEMO_PROJECTS, DemoAtom, AtomShelf, AtomStatus}; +use super::super::demo_data::{AtomShelf, AtomStatus, DEMO_PROJECTS, DemoAtom}; use super::super::state::SidebarViewModel; use super::super::surfaces::{ drop_line_color, pr_open_color, project_favicon_color, row_active_bg, row_hover_bg, @@ -24,10 +24,10 @@ use super::super::tokens::{ FAVICON_SIZE, ROW_CONTENT_INSET, ROW_HEIGHT_CARD, ROW_HEIGHT_SLIM, ROW_RADIUS, }; use super::callbacks::{ - AtomDragOverCallback, AtomDropCallback, AtomHoverCallback, AtomIdCallback, - AtomMoveCallback, AtomSelectCallback, + AtomDragOverCallback, AtomDropCallback, AtomHoverCallback, AtomIdCallback, AtomMoveCallback, + AtomSelectCallback, }; -use super::pinned_drag::{PinnedRowDragUi, PinnedAtomDrag, AtomDragScope}; +use super::pinned_drag::{AtomDragScope, PinnedAtomDrag, PinnedRowDragUi}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum AtomRowVariant { @@ -154,8 +154,7 @@ fn row_surface( let is_pinned = view.effective_shelf(atom) == AtomShelf::Pinned; let is_settled = view.effective_shelf(atom) == AtomShelf::Settled; let is_archived = view.is_archived(atom); - let drag_scope = - AtomDragScope::from_atom(atom.id, view.effective_shelf(atom), is_archived); + let drag_scope = AtomDragScope::from_atom(atom.id, view.effective_shelf(atom), is_archived); let menu_title = view.display_title(atom); let row_drag = pinned_drag.unwrap_or_default(); let drag_hover_bg = row_hover_bg(theme); @@ -232,11 +231,9 @@ fn row_surface( let self_id = atom_id.clone(); let self_scope = drag_scope; move |value, _, _| { - value - .downcast_ref::() - .is_some_and(|drag| { - drag.atom_id != self_id && drag.scope.allows_drop(self_scope) - }) + value.downcast_ref::().is_some_and(|drag| { + drag.atom_id != self_id && drag.scope.allows_drop(self_scope) + }) } }) .drag_over::({ @@ -736,8 +733,7 @@ fn terminal_indicator(atom: &DemoAtom, muted: gpui::Hsla) -> Vec Vec { - atom - .pr_number + atom.pr_number .map(|number| { div() .flex_shrink_0() diff --git a/src/app/shell/left_sidebar/content/draft_row.rs b/src/app/shell/left_sidebar/content/draft_row.rs index aa51fd8..f786d7c 100644 --- a/src/app/shell/left_sidebar/content/draft_row.rs +++ b/src/app/shell/left_sidebar/content/draft_row.rs @@ -19,7 +19,7 @@ use super::super::surfaces::{ }; use super::super::tokens::{FAVICON_SIZE, ROW_CONTENT_INSET, ROW_HEIGHT_CARD}; use super::callbacks::{AtomDragOverCallback, AtomDropCallback, AtomIdCallback}; -use super::pinned_drag::{PinnedRowDragUi, PinnedAtomDrag, AtomDragScope}; +use super::pinned_drag::{AtomDragScope, PinnedAtomDrag, PinnedRowDragUi}; pub struct DraftRowDragActions { pub on_drag_start: AtomIdCallback, @@ -127,11 +127,9 @@ pub fn sidebar_draft_row( .can_drop({ let self_id = draft_id.clone(); move |value, _, _| { - value - .downcast_ref::() - .is_some_and(|drag| { - drag.atom_id != self_id && drag.scope.allows_drop(drag_scope) - }) + value.downcast_ref::().is_some_and(|drag| { + drag.atom_id != self_id && drag.scope.allows_drop(drag_scope) + }) } }) .drag_over::({ diff --git a/src/app/shell/left_sidebar/content/mod.rs b/src/app/shell/left_sidebar/content/mod.rs index 4176e51..ee5eb65 100644 --- a/src/app/shell/left_sidebar/content/mod.rs +++ b/src/app/shell/left_sidebar/content/mod.rs @@ -1,3 +1,4 @@ +mod atom_row; mod callbacks; mod draft_row; mod empty_state; @@ -7,8 +8,8 @@ mod search_result_row; mod search_row; mod shelf_body; mod shelf_header; -mod atom_row; +pub use atom_row::{AtomRowActions, AtomRowVariant, sidebar_atom_row, sidebar_show_more_button}; pub use draft_row::{DraftRowDragActions, sidebar_draft_row}; pub use empty_state::{sidebar_add_project_button, sidebar_empty_state}; pub use pinned_drag::{PinnedDragState, PinnedRowDragUi}; @@ -17,6 +18,3 @@ pub use search_result_row::sidebar_search_result_row; pub use search_row::sidebar_search_row; pub use shelf_body::sidebar_shelf_body; pub use shelf_header::{ShelfTone, sidebar_section_header, sidebar_shelf_header}; -pub use atom_row::{ - AtomRowActions, AtomRowVariant, sidebar_show_more_button, sidebar_atom_row, -}; diff --git a/src/app/shell/left_sidebar/panel.rs b/src/app/shell/left_sidebar/panel.rs index c1a365a..eef80dc 100644 --- a/src/app/shell/left_sidebar/panel.rs +++ b/src/app/shell/left_sidebar/panel.rs @@ -20,13 +20,12 @@ use crate::shared::theme::{BackgroundToken, BorderToken, OpenCoreTheme, SpacingT use super::chrome::sidebar_chrome_footer; use super::content::{ - DraftRowDragActions, PinnedDragState, PinnedRowDragUi, ShelfTone, AtomRowActions, - AtomRowVariant, sidebar_add_project_button, sidebar_draft_row, sidebar_empty_state, - sidebar_project_scope_row, sidebar_search_result_row, sidebar_search_row, + AtomRowActions, AtomRowVariant, DraftRowDragActions, PinnedDragState, PinnedRowDragUi, + ShelfTone, sidebar_add_project_button, sidebar_atom_row, sidebar_draft_row, + sidebar_empty_state, sidebar_project_scope_row, sidebar_search_result_row, sidebar_search_row, sidebar_section_header, sidebar_shelf_body, sidebar_shelf_header, sidebar_show_more_button, - sidebar_atom_row, }; -use super::demo_data::{DEMO_DRAFT, DEMO_ATOMS}; +use super::demo_data::{DEMO_ATOMS, DEMO_DRAFT}; use super::shelf_tween::{ ShelfHeightTween, eval_shelf_tween, shelf_content_height_card, shelf_content_height_slim, shelf_expand_progress, @@ -548,12 +547,7 @@ impl LeftSidebarPanel { } } - fn begin_rename_atom( - &mut self, - atom_id: &str, - window: &mut Window, - cx: &mut Context, - ) { + fn begin_rename_atom(&mut self, atom_id: &str, window: &mut Window, cx: &mut Context) { let atom = super::demo_data::DEMO_ATOMS .iter() .find(|t| t.id == atom_id); diff --git a/src/app/shell/left_sidebar/state.rs b/src/app/shell/left_sidebar/state.rs index ebd823b..f405f1b 100644 --- a/src/app/shell/left_sidebar/state.rs +++ b/src/app/shell/left_sidebar/state.rs @@ -3,8 +3,8 @@ use std::collections::{HashMap, HashSet}; use super::demo_data::{ - ALL_ATOMS_LABEL, DEMO_DRAFT, DEMO_ATOMS, DEMO_PROJECTS, DemoDraft, DemoAtom, AtomShelf, - AtomStatus, + ALL_ATOMS_LABEL, AtomShelf, AtomStatus, DEMO_ATOMS, DEMO_DRAFT, DEMO_PROJECTS, DemoAtom, + DemoDraft, }; pub const SETTLED_PAGE_INITIAL: usize = 10; @@ -368,8 +368,7 @@ impl SidebarViewModel { if dragged_id == target_id { return false; } - if self.archived_atom_ids.contains(dragged_id) - && self.archived_atom_ids.contains(target_id) + if self.archived_atom_ids.contains(dragged_id) && self.archived_atom_ids.contains(target_id) { return true; } @@ -380,8 +379,7 @@ impl SidebarViewModel { if dragged_id == target_id { return; } - if self.archived_atom_ids.contains(dragged_id) - && self.archived_atom_ids.contains(target_id) + if self.archived_atom_ids.contains(dragged_id) && self.archived_atom_ids.contains(target_id) { reorder_ids( &mut self.archived_order, @@ -486,11 +484,7 @@ impl SidebarViewModel { fn reveal_settled_atom(&mut self, atom_id: &str) { let settled_len = self.settled_atoms().len(); - if let Some(pos) = self - .settled_atoms() - .iter() - .position(|t| t.id == atom_id) - { + if let Some(pos) = self.settled_atoms().iter().position(|t| t.id == atom_id) { let needed = pos + 1; if self.settled_visible_limit < needed { while self.settled_visible_limit < needed && self.settled_has_more() { @@ -553,8 +547,7 @@ impl SidebarViewModel { fn move_atom_to_shelf(&mut self, atom_id: &str, shelf: AtomShelf) { self.remove_from_shelf_orders(atom_id); - self.atom_shelf_overrides - .insert(atom_id.to_string(), shelf); + self.atom_shelf_overrides.insert(atom_id.to_string(), shelf); self.append_to_shelf_order(atom_id, shelf); } From fdc304b5f746189b750eb9159b70683ace7d444f Mon Sep 17 00:00:00 2001 From: Bambang Tri Rahmat Doni Date: Wed, 2 Sep 2026 18:14:59 +0700 Subject: [PATCH 6/7] feat(shell): add stateful workspace composer toolbar. Introduce a t3.codes-style composer with dropdown controls, live selection state, and rectangular theme-aligned context and send controls. --- src/app/shell/main_workspace_panel.rs | 396 ++++++++++++++++++++++++-- 1 file changed, 368 insertions(+), 28 deletions(-) diff --git a/src/app/shell/main_workspace_panel.rs b/src/app/shell/main_workspace_panel.rs index 7785b93..52f0adc 100644 --- a/src/app/shell/main_workspace_panel.rs +++ b/src/app/shell/main_workspace_panel.rs @@ -3,19 +3,21 @@ use gpui::{ App, AppContext, Context, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Window, div, px, - relative, + prelude::FluentBuilder, relative, }; use gpui_component::{ - IconName, Sizable, + Icon, IconName, Sizable, button::{Button, ButtonRounded, ButtonVariants as _}, dock::{Panel, PanelEvent}, h_flex, input::{Input, InputEvent, InputState}, + menu::{DropdownMenu as _, PopupMenu, PopupMenuItem}, v_flex, }; use crate::shared::theme::{ - BackgroundToken, BorderToken, ForegroundToken, OpenCoreTheme, SpacingToken, TypeRole, + ActionToken, BackgroundToken, BorderToken, ForegroundToken, OpenCoreTheme, SpacingToken, + TypeRole, }; use super::workspace_theme::WorkspaceTheme; @@ -30,12 +32,47 @@ pub struct MainWorkspacePanel { focus_handle: FocusHandle, theme: WorkspaceTheme, input: gpui::Entity, + composer: ComposerToolbarState, + context_percent: u32, _input_subscription: Subscription, } const COMPOSER_MIN_ROWS: usize = 1; const COMPOSER_MAX_ROWS: usize = 6; +const MODEL_OPTIONS: &[&str] = &["Claude Opus 4.5", "Claude Sonnet 4", "GPT-5"]; +const PRIORITY_OPTIONS: &[&str] = &["High", "Normal"]; +const MODE_OPTIONS: &[&str] = &["Build", "Plan", "Ask"]; +const ACCESS_OPTIONS: &[&str] = &["Full access", "Read only", "Ask before edits"]; +const DEFAULT_CONTEXT_PERCENT: u32 = 85; + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ComposerToolbarState { + model: String, + priority: String, + mode: String, + access: String, +} + +impl Default for ComposerToolbarState { + fn default() -> Self { + Self { + model: MODEL_OPTIONS[0].to_string(), + priority: PRIORITY_OPTIONS[0].to_string(), + mode: MODE_OPTIONS[0].to_string(), + access: ACCESS_OPTIONS[0].to_string(), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum ComposerField { + Model, + Priority, + Mode, + Access, +} + impl MainWorkspacePanel { pub fn new(window: &mut Window, theme: WorkspaceTheme, cx: &mut Context) -> Self { let input = cx.new(|cx| { @@ -53,10 +90,22 @@ impl MainWorkspacePanel { focus_handle: cx.focus_handle(), theme, input, + composer: ComposerToolbarState::default(), + context_percent: DEFAULT_CONTEXT_PERCENT, _input_subscription, } } + fn select_composer_option(&mut self, field: ComposerField, value: String, cx: &mut Context) { + match field { + ComposerField::Model => self.composer.model = value, + ComposerField::Priority => self.composer.priority = value, + ComposerField::Mode => self.composer.mode = value, + ComposerField::Access => self.composer.access = value, + } + cx.notify(); + } + fn submit_composer(&mut self, window: &mut Window, cx: &mut Context) { let text = self.input.read(cx).value().trim().to_string(); if text.is_empty() { @@ -146,7 +195,7 @@ impl Render for MainWorkspacePanel { )) .child(quick_actions_row(tertiary, border, primary)), ) - .child(composer_bar(cx, &self.input, &theme, mono, pad)) + .child(composer_bar(self, cx, &self.input, &theme, mono, pad)) } } @@ -226,20 +275,31 @@ fn quick_actions_row( } fn composer_bar( + panel: &MainWorkspacePanel, cx: &mut Context, input: &gpui::Entity, theme: &OpenCoreTheme, mono: SharedString, pad: f32, ) -> impl IntoElement { - const COMPOSER_MIN_HEIGHT: f32 = 56.; + const COMPOSER_MIN_HEIGHT: f32 = 72.; const COMPOSER_TEXT: f32 = 16.; + const TOOLBAR_HEIGHT: f32 = 40.; + const TOOLBAR_BUTTON_HEIGHT: f32 = 32.; + const TOOLBAR_BUTTON_PX: f32 = 8.; + const CONTROL_HEIGHT: f32 = 32.; + const CONTEXT_WIDTH: f32 = 36.; let surface = theme.surface(BackgroundToken::Secondary); let border = theme.border_token(BorderToken::Default); let border_strong = theme.border_token(BorderToken::Strong); let primary = theme.foreground(ForegroundToken::Primary); + let secondary = theme.foreground(ForegroundToken::Secondary); let muted = theme.foreground(ForegroundToken::Muted); + let action_bg = theme.action(ActionToken::Strong); + let action_fg = theme.action(ActionToken::StrongText); + let sans = sans_family(); + let panel_entity = cx.entity().clone(); v_flex() .w_full() @@ -250,43 +310,104 @@ fn composer_bar( .border_t_1() .border_color(border) .child( - h_flex() + v_flex() .w_full() - .gap(px(SpacingToken::S1.value())) - .items_end() + .border_1() + .border_color(border) + .bg(surface) .child( div() - .flex_1() - .min_w_0() + .w_full() .min_h(px(COMPOSER_MIN_HEIGHT)) + .px(px(SpacingToken::S3.value())) + .pt(px(SpacingToken::S3.value())) + .pb(px(SpacingToken::S1.value())) .child( Input::new(input) .large() .w_full() .text_size(px(COMPOSER_TEXT)) - .bordered(true) - .appearance(true) + .bordered(false) + .appearance(false) .cleanable(false), ), ) + .child(div().w_full().h(px(1.)).bg(border)) .child( - div() - .id("workspace-composer-send") - .debug_selector(|| "workspace-composer-send".to_string()) + h_flex() + .w_full() + .h(px(TOOLBAR_HEIGHT)) + .px(px(SpacingToken::S3.value())) + .py(px(2.)) + .items_center() + .gap(px(2.)) + .child(composer_model_menu( + &panel.composer.model, + panel_entity.clone(), + primary, + secondary, + sans.clone(), + TOOLBAR_BUTTON_HEIGHT, + TOOLBAR_BUTTON_PX, + )) + .child(composer_toolbar_divider(border)) + .child(composer_priority_menu( + &panel.composer.priority, + panel_entity.clone(), + primary, + sans.clone(), + TOOLBAR_BUTTON_HEIGHT, + TOOLBAR_BUTTON_PX, + )) + .child(composer_toolbar_divider(border)) + .child(composer_build_menu( + &panel.composer.mode, + panel_entity.clone(), + primary, + secondary, + sans.clone(), + TOOLBAR_BUTTON_HEIGHT, + TOOLBAR_BUTTON_PX, + )) + .child(composer_toolbar_divider(border)) + .child(composer_access_menu( + &panel.composer.access, + panel_entity.clone(), + primary, + secondary, + sans, + TOOLBAR_BUTTON_HEIGHT, + TOOLBAR_BUTTON_PX, + )) + .child(div().flex_1().min_w(px(8.))) + .child(composer_context_badge( + primary, + surface, + border_strong, + mono.clone(), + CONTROL_HEIGHT, + CONTEXT_WIDTH, + panel.context_percent, + )) .child( - Button::new("workspace-send") - .ghost() - .rounded(ButtonRounded::None) - .icon(IconName::ArrowUp) - .h(px(COMPOSER_MIN_HEIGHT)) - .w(px(COMPOSER_MIN_HEIGHT)) - .text_color(primary) - .border_1() - .border_color(border_strong) - .bg(surface) - .on_click(cx.listener(|this, _, window, cx| { - this.submit_composer(window, cx); - })), + div() + .id("workspace-composer-send") + .debug_selector(|| "workspace-composer-send".to_string()) + .ml(px(SpacingToken::S1.value())) + .child( + Button::new("workspace-send") + .ghost() + .rounded(ButtonRounded::None) + .icon(Icon::new(IconName::ArrowUp).text_color(action_fg)) + .h(px(CONTROL_HEIGHT)) + .w(px(CONTROL_HEIGHT)) + .border_1() + .border_color(border_strong) + .bg(action_bg) + .on_click(cx.listener(|this, _, window, cx| { + this.submit_composer(window, cx); + })), + ), ), ), ) @@ -299,6 +420,225 @@ fn composer_bar( ) } +fn composer_toolbar_divider(color: gpui::Hsla) -> impl IntoElement { + div() + .mx(px(SpacingToken::S1.value())) + .w(px(1.)) + .h(px(18.)) + .flex_shrink_0() + .bg(color.alpha(0.75)) +} + +fn composer_toolbar_label( + label: impl Into, + primary: gpui::Hsla, + sans: SharedString, +) -> impl IntoElement { + div() + .flex_shrink_0() + .mr(px(2.)) + .font_family(sans) + .text_size(px(TypeRole::LabelMd.size())) + .line_height(relative(TypeRole::LabelMd.line_height())) + .text_color(primary) + .child(label.into()) +} + +fn composer_section_menu( + menu: PopupMenu, + section: impl Into, + options: &[&'static str], + selected: &str, + field: ComposerField, + panel: gpui::Entity, +) -> PopupMenu { + let mut menu = menu.label(section).separator(); + for option in options { + let option = (*option).to_string(); + let panel = panel.clone(); + let checked = option == selected; + menu = menu.item( + PopupMenuItem::new(option.clone()) + .checked(checked) + .on_click(move |_, _, cx| { + panel.update(cx, |panel, cx| { + panel.select_composer_option(field, option.clone(), cx); + }); + }), + ); + } + menu +} + +fn composer_dropdown_button( + id: &'static str, + label: &str, + selected: &str, + panel: gpui::Entity, + primary: gpui::Hsla, + icon: Option<(IconName, gpui::Hsla)>, + sans: SharedString, + button_height: f32, + button_px: f32, + section: &'static str, + options: &'static [&'static str], + field: ComposerField, +) -> impl IntoElement { + let selected = selected.to_string(); + let panel_for_menu = panel.clone(); + Button::new(id) + .ghost() + .compact() + .rounded(ButtonRounded::None) + .h(px(button_height)) + .px(px(button_px)) + .gap(px(6.)) + .text_color(primary) + .when_some(icon, |this, (icon, color)| { + this.icon(Icon::new(icon).text_color(color).small()) + }) + .child(composer_toolbar_label(label, primary, sans)) + .dropdown_caret(true) + .dropdown_menu(move |menu, _, _| { + composer_section_menu( + menu, + section, + options, + &selected, + field, + panel_for_menu.clone(), + ) + }) +} + +fn composer_model_menu( + selected: &str, + panel: gpui::Entity, + primary: gpui::Hsla, + secondary: gpui::Hsla, + sans: SharedString, + button_height: f32, + button_px: f32, +) -> impl IntoElement { + composer_dropdown_button( + "workspace-composer-model", + selected, + selected, + panel, + primary, + Some((IconName::Cpu, secondary)), + sans, + button_height, + button_px, + "Model", + MODEL_OPTIONS, + ComposerField::Model, + ) +} + +fn composer_priority_menu( + selected: &str, + panel: gpui::Entity, + primary: gpui::Hsla, + sans: SharedString, + button_height: f32, + button_px: f32, +) -> impl IntoElement { + composer_dropdown_button( + "workspace-composer-priority", + selected, + selected, + panel, + primary, + None, + sans, + button_height, + button_px, + "Priority", + PRIORITY_OPTIONS, + ComposerField::Priority, + ) +} + +fn composer_build_menu( + selected: &str, + panel: gpui::Entity, + primary: gpui::Hsla, + secondary: gpui::Hsla, + sans: SharedString, + button_height: f32, + button_px: f32, +) -> impl IntoElement { + composer_dropdown_button( + "workspace-composer-build", + selected, + selected, + panel, + primary, + Some((IconName::Bot, secondary)), + sans, + button_height, + button_px, + "Mode", + MODE_OPTIONS, + ComposerField::Mode, + ) +} + +fn composer_access_menu( + selected: &str, + panel: gpui::Entity, + primary: gpui::Hsla, + secondary: gpui::Hsla, + sans: SharedString, + button_height: f32, + button_px: f32, +) -> impl IntoElement { + composer_dropdown_button( + "workspace-composer-access", + selected, + selected, + panel, + primary, + Some((IconName::Eye, secondary)), + sans, + button_height, + button_px, + "Access", + ACCESS_OPTIONS, + ComposerField::Access, + ) +} + +fn composer_context_badge( + primary: gpui::Hsla, + surface: gpui::Hsla, + border: gpui::Hsla, + mono: SharedString, + height: f32, + width: f32, + percent: u32, +) -> impl IntoElement { + div() + .id("workspace-composer-context") + .w(px(width)) + .h(px(height)) + .flex_shrink_0() + .flex() + .items_center() + .justify_center() + .border_1() + .border_color(border) + .bg(surface) + .child( + div() + .font_family(mono) + .text_size(px(TypeRole::MonoSm.size())) + .text_color(primary) + .child(format!("{percent}")), + ) +} + fn mono_family() -> SharedString { SharedString::from("Space Mono") } From d76516268285bebe0ffcfe4e59a5cb87d341d3ec Mon Sep 17 00:00:00 2001 From: Bambang Tri Rahmat Doni Date: Wed, 2 Sep 2026 18:17:15 +0700 Subject: [PATCH 7/7] style(shell): fix composer toolbar fmt and clippy. Apply rustfmt and collapse dropdown button args into a spec struct so CI fmt and clippy pass. --- src/app/shell/main_workspace_panel.rs | 238 +++++++++----------------- 1 file changed, 81 insertions(+), 157 deletions(-) diff --git a/src/app/shell/main_workspace_panel.rs b/src/app/shell/main_workspace_panel.rs index 52f0adc..695810e 100644 --- a/src/app/shell/main_workspace_panel.rs +++ b/src/app/shell/main_workspace_panel.rs @@ -2,8 +2,8 @@ use gpui::{ App, AppContext, Context, EventEmitter, FocusHandle, Focusable, InteractiveElement, - IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Window, div, px, - prelude::FluentBuilder, relative, + IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Window, div, + prelude::FluentBuilder, px, relative, }; use gpui_component::{ Icon, IconName, Sizable, @@ -73,6 +73,20 @@ enum ComposerField { Access, } +struct ComposerDropdownSpec<'a> { + id: &'static str, + label: &'a str, + panel: gpui::Entity, + primary: gpui::Hsla, + icon: Option<(IconName, gpui::Hsla)>, + sans: SharedString, + button_height: f32, + button_px: f32, + section: &'static str, + options: &'static [&'static str], + field: ComposerField, +} + impl MainWorkspacePanel { pub fn new(window: &mut Window, theme: WorkspaceTheme, cx: &mut Context) -> Self { let input = cx.new(|cx| { @@ -96,7 +110,12 @@ impl MainWorkspacePanel { } } - fn select_composer_option(&mut self, field: ComposerField, value: String, cx: &mut Context) { + fn select_composer_option( + &mut self, + field: ComposerField, + value: String, + cx: &mut Context, + ) { match field { ComposerField::Model => self.composer.model = value, ComposerField::Priority => self.composer.priority = value, @@ -341,44 +360,61 @@ fn composer_bar( .py(px(2.)) .items_center() .gap(px(2.)) - .child(composer_model_menu( - &panel.composer.model, - panel_entity.clone(), + .child(composer_dropdown_button(ComposerDropdownSpec { + id: "workspace-composer-model", + label: &panel.composer.model, + panel: panel_entity.clone(), primary, - secondary, - sans.clone(), - TOOLBAR_BUTTON_HEIGHT, - TOOLBAR_BUTTON_PX, - )) + icon: Some((IconName::Cpu, secondary)), + sans: sans.clone(), + button_height: TOOLBAR_BUTTON_HEIGHT, + button_px: TOOLBAR_BUTTON_PX, + section: "Model", + options: MODEL_OPTIONS, + field: ComposerField::Model, + })) .child(composer_toolbar_divider(border)) - .child(composer_priority_menu( - &panel.composer.priority, - panel_entity.clone(), + .child(composer_dropdown_button(ComposerDropdownSpec { + id: "workspace-composer-priority", + label: &panel.composer.priority, + panel: panel_entity.clone(), primary, - sans.clone(), - TOOLBAR_BUTTON_HEIGHT, - TOOLBAR_BUTTON_PX, - )) + icon: None, + sans: sans.clone(), + button_height: TOOLBAR_BUTTON_HEIGHT, + button_px: TOOLBAR_BUTTON_PX, + section: "Priority", + options: PRIORITY_OPTIONS, + field: ComposerField::Priority, + })) .child(composer_toolbar_divider(border)) - .child(composer_build_menu( - &panel.composer.mode, - panel_entity.clone(), + .child(composer_dropdown_button(ComposerDropdownSpec { + id: "workspace-composer-build", + label: &panel.composer.mode, + panel: panel_entity.clone(), primary, - secondary, - sans.clone(), - TOOLBAR_BUTTON_HEIGHT, - TOOLBAR_BUTTON_PX, - )) + icon: Some((IconName::Bot, secondary)), + sans: sans.clone(), + button_height: TOOLBAR_BUTTON_HEIGHT, + button_px: TOOLBAR_BUTTON_PX, + section: "Mode", + options: MODE_OPTIONS, + field: ComposerField::Mode, + })) .child(composer_toolbar_divider(border)) - .child(composer_access_menu( - &panel.composer.access, - panel_entity.clone(), + .child(composer_dropdown_button(ComposerDropdownSpec { + id: "workspace-composer-access", + label: &panel.composer.access, + panel: panel_entity.clone(), primary, - secondary, + icon: Some((IconName::Eye, secondary)), sans, - TOOLBAR_BUTTON_HEIGHT, - TOOLBAR_BUTTON_PX, - )) + button_height: TOOLBAR_BUTTON_HEIGHT, + button_px: TOOLBAR_BUTTON_PX, + section: "Access", + options: ACCESS_OPTIONS, + field: ComposerField::Access, + })) .child(div().flex_1().min_w(px(8.))) .child(composer_context_badge( primary, @@ -470,146 +506,34 @@ fn composer_section_menu( menu } -fn composer_dropdown_button( - id: &'static str, - label: &str, - selected: &str, - panel: gpui::Entity, - primary: gpui::Hsla, - icon: Option<(IconName, gpui::Hsla)>, - sans: SharedString, - button_height: f32, - button_px: f32, - section: &'static str, - options: &'static [&'static str], - field: ComposerField, -) -> impl IntoElement { - let selected = selected.to_string(); - let panel_for_menu = panel.clone(); - Button::new(id) +fn composer_dropdown_button(spec: ComposerDropdownSpec<'_>) -> impl IntoElement { + let selected = spec.label.to_string(); + let panel_for_menu = spec.panel.clone(); + Button::new(spec.id) .ghost() .compact() .rounded(ButtonRounded::None) - .h(px(button_height)) - .px(px(button_px)) + .h(px(spec.button_height)) + .px(px(spec.button_px)) .gap(px(6.)) - .text_color(primary) - .when_some(icon, |this, (icon, color)| { + .text_color(spec.primary) + .when_some(spec.icon, |this, (icon, color)| { this.icon(Icon::new(icon).text_color(color).small()) }) - .child(composer_toolbar_label(label, primary, sans)) + .child(composer_toolbar_label(spec.label, spec.primary, spec.sans)) .dropdown_caret(true) .dropdown_menu(move |menu, _, _| { composer_section_menu( menu, - section, - options, + spec.section, + spec.options, &selected, - field, + spec.field, panel_for_menu.clone(), ) }) } -fn composer_model_menu( - selected: &str, - panel: gpui::Entity, - primary: gpui::Hsla, - secondary: gpui::Hsla, - sans: SharedString, - button_height: f32, - button_px: f32, -) -> impl IntoElement { - composer_dropdown_button( - "workspace-composer-model", - selected, - selected, - panel, - primary, - Some((IconName::Cpu, secondary)), - sans, - button_height, - button_px, - "Model", - MODEL_OPTIONS, - ComposerField::Model, - ) -} - -fn composer_priority_menu( - selected: &str, - panel: gpui::Entity, - primary: gpui::Hsla, - sans: SharedString, - button_height: f32, - button_px: f32, -) -> impl IntoElement { - composer_dropdown_button( - "workspace-composer-priority", - selected, - selected, - panel, - primary, - None, - sans, - button_height, - button_px, - "Priority", - PRIORITY_OPTIONS, - ComposerField::Priority, - ) -} - -fn composer_build_menu( - selected: &str, - panel: gpui::Entity, - primary: gpui::Hsla, - secondary: gpui::Hsla, - sans: SharedString, - button_height: f32, - button_px: f32, -) -> impl IntoElement { - composer_dropdown_button( - "workspace-composer-build", - selected, - selected, - panel, - primary, - Some((IconName::Bot, secondary)), - sans, - button_height, - button_px, - "Mode", - MODE_OPTIONS, - ComposerField::Mode, - ) -} - -fn composer_access_menu( - selected: &str, - panel: gpui::Entity, - primary: gpui::Hsla, - secondary: gpui::Hsla, - sans: SharedString, - button_height: f32, - button_px: f32, -) -> impl IntoElement { - composer_dropdown_button( - "workspace-composer-access", - selected, - selected, - panel, - primary, - Some((IconName::Eye, secondary)), - sans, - button_height, - button_px, - "Access", - ACCESS_OPTIONS, - ComposerField::Access, - ) -} - fn composer_context_badge( primary: gpui::Hsla, surface: gpui::Hsla,