commit e79bbd92ef7f51c858de6618c73bbbbdbda121c4
parent 2c6302fe32e61cb76a4ddf7f56b9dccdf7cca3bb
Author: William Casarin <jb55@jb55.com>
Date: Sat, 24 Jan 2026 16:39:12 -0800
dave: restyle session list sidebar with ChatGPT-like design
- Add darker background to sidebar using faint_bg_color
- Add padding (8px horizontal, 12px vertical) to sidebar frame
- Simplify session items to single-line (title only, no preview)
- Add hover highlight effect on session items
- Add pointer cursor on hover
- Remove separator between header and list
- Use manual painting for cleaner item rendering
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat:
2 files changed, 37 insertions(+), 70 deletions(-)
diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs
@@ -222,7 +222,11 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
// Render sidebar first - borrow released after this
let session_action = ui
.allocate_new_ui(egui::UiBuilder::new().max_rect(sidebar_rect), |ui| {
- SessionListUi::new(&self.session_manager).ui(ui)
+ egui::Frame::new()
+ .fill(ui.visuals().faint_bg_color)
+ .inner_margin(egui::Margin::symmetric(8, 12))
+ .show(ui, |ui| SessionListUi::new(&self.session_manager).ui(ui))
+ .inner
})
.inner;
@@ -258,7 +262,12 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
fn narrow_ui(&mut self, app_ctx: &mut AppContext, ui: &mut egui::Ui) -> DaveResponse {
if self.show_session_list {
// Show session list
- if let Some(action) = SessionListUi::new(&self.session_manager).ui(ui) {
+ let session_action = egui::Frame::new()
+ .fill(ui.visuals().faint_bg_color)
+ .inner_margin(egui::Margin::symmetric(8, 12))
+ .show(ui, |ui| SessionListUi::new(&self.session_manager).ui(ui))
+ .inner;
+ if let Some(action) = session_action {
match action {
SessionListAction::NewSession => {
self.session_manager.new_session();
diff --git a/crates/notedeck_dave/src/ui/session_list.rs b/crates/notedeck_dave/src/ui/session_list.rs
@@ -1,7 +1,6 @@
use egui::{Align, Layout, Sense};
use crate::session::{SessionId, SessionManager};
-use crate::Message;
/// Actions that can be triggered from the session list UI
#[derive(Debug, Clone)]
@@ -29,8 +28,6 @@ impl<'a> SessionListUi<'a> {
action = self.header_ui(ui);
ui.add_space(8.0);
- ui.separator();
- ui.add_space(8.0);
// Scrollable list of sessions
egui::ScrollArea::vertical()
@@ -49,13 +46,13 @@ impl<'a> SessionListUi<'a> {
let mut action = None;
ui.horizontal(|ui| {
- ui.add_space(12.0);
- ui.heading("Chats");
+ ui.add_space(4.0);
+ ui.label(egui::RichText::new("Chats").size(18.0).strong());
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
- ui.add_space(12.0);
+ ui.add_space(4.0);
if ui
- .button("+")
+ .button(egui::RichText::new("+").size(18.0))
.on_hover_text("New Chat")
.clicked()
{
@@ -74,7 +71,7 @@ impl<'a> SessionListUi<'a> {
for session in self.session_manager.sessions_ordered() {
let is_active = Some(session.id) == active_id;
- let response = self.session_item_ui(ui, &session.title, Self::get_preview(&session.chat), is_active);
+ let response = self.session_item_ui(ui, &session.title, is_active);
if response.clicked() {
action = Some(SessionListAction::SwitchTo(session.id));
@@ -92,72 +89,33 @@ impl<'a> SessionListUi<'a> {
action
}
- fn session_item_ui(
- &self,
- ui: &mut egui::Ui,
- title: &str,
- preview: Option<String>,
- is_active: bool,
- ) -> egui::Response {
+ fn session_item_ui(&self, ui: &mut egui::Ui, title: &str, is_active: bool) -> egui::Response {
+ let desired_size = egui::vec2(ui.available_width(), 36.0);
+ let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
+ let response = response.on_hover_cursor(egui::CursorIcon::PointingHand);
+
+ // Paint background: active > hovered > transparent
let fill = if is_active {
ui.visuals().widgets.active.bg_fill
+ } else if response.hovered() {
+ ui.visuals().widgets.hovered.weak_bg_fill
} else {
egui::Color32::TRANSPARENT
};
- let frame = egui::Frame::new()
- .fill(fill)
- .inner_margin(egui::Margin::symmetric(12, 8))
- .corner_radius(8.0);
-
- frame
- .show(ui, |ui| {
- ui.set_width(ui.available_width());
-
- ui.vertical(|ui| {
- // Title
- ui.add(
- egui::Label::new(
- egui::RichText::new(title)
- .strong()
- .size(14.0),
- )
- .truncate(),
- );
-
- // Preview of last message
- if let Some(preview) = preview {
- ui.add(
- egui::Label::new(
- egui::RichText::new(preview)
- .weak()
- .size(12.0),
- )
- .truncate(),
- );
- }
- });
- })
- .response
- .interact(Sense::click())
- }
+ let corner_radius = 8.0;
+ ui.painter().rect_filled(rect, corner_radius, fill);
- /// Get a preview string from the chat history
- fn get_preview(chat: &[Message]) -> Option<String> {
- // Find the last user or assistant message
- for msg in chat.iter().rev() {
- match msg {
- Message::User(text) | Message::Assistant(text) => {
- let preview: String = text.chars().take(50).collect();
- return Some(if text.len() > 50 {
- format!("{}...", preview)
- } else {
- preview
- });
- }
- _ => continue,
- }
- }
- None
+ // Draw title text (left-aligned, vertically centered)
+ let text_pos = rect.left_center() + egui::vec2(8.0, 0.0);
+ ui.painter().text(
+ text_pos,
+ egui::Align2::LEFT_CENTER,
+ title,
+ egui::FontId::proportional(14.0),
+ ui.visuals().text_color(),
+ );
+
+ response
}
}