gui: add a placeholder notes search bar to the header
Build Debug APK / build (push) Failing after 17m21s

Adds NotesSearchBar as its own component (rust/gui-app/src/agents/notes/search_bar.rs)
so a richer search interface can grow there later. Moves the note title/timestamps
out of the shared header into a compact bordered row atop the content column,
freeing the header for the "Notes" label plus the search bar, which is left-aligned
with the content column and capped at 500px wide.
This commit is contained in:
Greg Shuflin
2026-07-08 02:17:18 -07:00
parent 5490ea50c4
commit 19465c227a
2 changed files with 82 additions and 16 deletions
+42 -16
View File
@@ -9,6 +9,7 @@
//! executor and folds back into the view via `Entity::update`.
pub mod editor;
pub mod search_bar;
use std::collections::HashMap;
use std::sync::Arc;
@@ -25,6 +26,7 @@ use synchronicity::node::NodePubkey;
use synchronicity::stores::Store;
use self::editor::{NoteEditor, NoteEditorEvent};
use self::search_bar::NotesSearchBar;
use crate::agent_sidebar::{AgentSidebar, View};
use crate::components::button::button;
use crate::components::hierarchical_sidebar::{
@@ -143,6 +145,8 @@ pub struct NotesView {
_context_menu_sub: Option<Subscription>,
dialog: Option<Dialog>,
error: Option<SharedString>,
/// Placeholder search bar shown at the right edge of the header.
search_bar: Entity<NotesSearchBar>,
}
impl NotesView {
@@ -179,6 +183,8 @@ impl NotesView {
})
.detach();
let search_bar = cx.new(NotesSearchBar::new);
let mut this = Self {
store,
node,
@@ -201,6 +207,7 @@ impl NotesView {
_context_menu_sub: None,
dialog: None,
error: None,
search_bar,
};
this.reload_notebooks(None, cx);
this
@@ -662,12 +669,22 @@ impl Render for NotesView {
self.ensure_context_menu(window, cx);
let dragging = self.sidebar_resize.is_dragging();
let content_column = div()
.flex()
.flex_col()
.flex_1()
.min_w(px(0.))
.min_h(px(0.))
.when_some(self.note_meta.as_ref(), |c, meta| {
c.child(self.render_note_meta_row(&theme, meta, tz))
})
.child(self.render_content(&theme));
let root = div()
.relative()
.flex()
.flex_col()
.size_full()
.child(self.render_header(&theme, tz))
.child(self.render_header(&theme))
.child(
div()
.flex()
@@ -682,7 +699,7 @@ impl Render for NotesView {
cx,
|this| &mut this.sidebar_resize,
))
.child(self.render_content(&theme)),
.child(content_column),
)
.when_some(self.render_context_menu(), |c, menu| c.child(menu))
.when(self.dialog.is_some(), |c| c.child(self.render_dialog(&theme, cx)))
@@ -692,7 +709,12 @@ impl Render for NotesView {
}
impl NotesView {
fn render_header(&self, theme: &Theme, tz: Option<chrono_tz::Tz>) -> impl IntoElement + use<> {
/// The top bar: the "Notes" label over the sidebar column, and the search
/// bar starting at the content column's left edge (same as the note meta
/// row below it). The note meta (title + timestamps) used to live here but
/// now sits in its own compact row atop the content column, so it stays
/// clear of the sidebar.
fn render_header(&self, theme: &Theme) -> impl IntoElement + use<> {
div()
.flex()
.flex_row()
@@ -703,7 +725,7 @@ impl NotesView {
.border_color(theme.border_default)
.child(
// The "Notes" label sits over the sidebar column; its width plus the
// resize divider lines up the note meta with the content pane's edge.
// resize divider lines up the content column below with its edge.
div()
.w(self.sidebar_resize.width() + px(resizable::DIVIDER_WIDTH))
.flex_none()
@@ -713,16 +735,16 @@ impl NotesView {
.text_color(theme.text_primary)
.child("Notes"),
)
.when_some(self.note_meta.as_ref(), |c, meta| {
c.child(self.render_note_meta(theme, meta, tz))
})
.child(div().flex_1().min_w(px(0.)).flex().px_4().child(self.search_bar.clone()))
}
/// The active note's title with its created and last-modified timestamps
/// beneath, shown in the header bar starting at the content pane's left edge.
/// A compact row atop the content column showing the open note's title and
/// its created/modified timestamps on a single line, bordered below to
/// separate it from the note body. Bounded on the left by the sidebar since
/// it lives inside the content column, not the shared header.
/// The title is read live from [`Self::items`] (the tree's single source of
/// truth) so an inline rename is reflected without re-selecting the note.
fn render_note_meta(
fn render_note_meta_row(
&self,
theme: &Theme,
meta: &NoteMeta,
@@ -735,15 +757,18 @@ impl NotesView {
.map(|info| info.title.clone())
.unwrap_or_else(|| DEFAULT_ITEM_NAME.into());
div()
.flex_none()
.flex()
.flex_col()
.justify_center()
.gap_1()
.flex_1()
.min_w_0()
.flex_row()
.items_center()
.gap_3()
.h(px(36.))
.px_4()
.border_b_1()
.border_color(theme.border_default)
.child(
div()
.flex_none()
.text_base()
.font_weight(FontWeight::BOLD)
.text_color(theme.text_primary)
@@ -755,7 +780,8 @@ impl NotesView {
div()
.flex()
.flex_row()
.gap_4()
.flex_none()
.gap_3()
.text_size(px(12.))
.text_color(theme.text_secondary)
.child(div().child(SharedString::from(format!(
@@ -0,0 +1,40 @@
//! The notes search bar shown at the right edge of the header.
//!
//! Currently just a text field with no wired-up behavior — a placeholder for
//! what should grow into a richer search interface (scoping to a notebook,
//! filters, live results, etc.). It's kept as its own component from the start
//! so that growth happens here rather than in [`super::NotesView`].
use gpui::{Context, Entity, IntoElement, Render, Window, div, prelude::*, px};
use crate::components::text_input::TextInput;
/// A search field this wide gives room for a richer query (filters, scoped
/// terms) while staying well under the ~600px line length past which a
/// single-line field gets hard to scan; it also keeps the bar from
/// stretching across ultrawide monitors.
const MAX_WIDTH: f32 = 500.;
pub struct NotesSearchBar {
input: Entity<TextInput>,
}
impl NotesSearchBar {
pub fn new(cx: &mut Context<Self>) -> Self {
let input = cx.new(|cx| TextInput::new(cx, "Search notes…"));
Self { input }
}
}
impl Render for NotesSearchBar {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div()
.flex()
.flex_row()
.items_center()
.w_full()
.max_w(px(MAX_WIDTH))
.min_w(px(0.))
.child(self.input.clone())
}
}