feat: add event fetching strategy (#24)
Rust / build (macos-latest, stable) (push) Canceled after 0s
Rust / build (ubuntu-latest, stable) (push) Canceled after 0s
Rust / build (windows-latest, stable) (push) Canceled after 0s

Reviewed-on: #24
This commit was merged in pull request #24.
This commit is contained in:
2026-09-27 02:50:11 +00:00
parent da22d26019
commit fa50e9f153
21 changed files with 484 additions and 399 deletions
@@ -303,6 +303,7 @@ pub struct CommitDiffView {
loading: bool,
error: Option<SharedString>,
pane: Entity<DiffPane>,
tasks: Vec<gpui::Task<Result<(), anyhow::Error>>>,
}
impl CommitDiffView {
@@ -336,6 +337,7 @@ impl CommitDiffView {
loading: true,
error: None,
pane,
tasks: Vec::new(),
}
}
@@ -383,7 +385,7 @@ impl CommitDiffView {
Ok(())
});
task.detach();
self.tasks.push(task);
}
fn render_header(&self, cx: &mut Context<Self>) -> AnyElement {
+9 -22
View File
@@ -11,7 +11,7 @@ use gpui::{
use gpui_component::button::{Button, ButtonVariants};
use gpui_component::{ActiveTheme, Icon, IconName, IconNamed, Sizable, StyledExt, h_flex, v_flex};
use nostr::prelude::{Event, EventId, Kind, PublicKey, Timestamp};
use signed_core::{COVER_NOTE_KIND, InboxItem, InboxReadState, RepoAddr, filters};
use signed_core::{InboxItem, InboxReadState, RepoAddr};
use signed_state::{
Backend, BackendEvent, ProfileStore, RefreshGate, RefreshRequest, RepoListStore, query_inbox,
};
@@ -157,21 +157,8 @@ impl InboxView {
fn handle_backend_event(&mut self, event: &BackendEvent, cx: &mut Context<Self>) {
match event {
BackendEvent::NostrUpdate(updates) => {
let relevant = updates.iter().any(|update| {
let is_notification = filters::NOTIFICATION_KINDS.contains(&update.kind);
let is_comment = update.kind == Kind::Comment;
let is_event_deletion = update.kind == Kind::EventDeletion;
let is_request_to_vanish = update.kind == Kind::RequestToVanish;
is_notification || is_comment || is_event_deletion || is_request_to_vanish
});
if relevant {
self.refresh(cx);
}
}
BackendEvent::Synced | BackendEvent::Published(_) => self.refresh(cx),
BackendEvent::RepoUpdates(_) => self.refresh(cx),
BackendEvent::Synced => self.refresh(cx),
_ => {}
}
}
@@ -200,12 +187,16 @@ impl InboxView {
self.refresh.begin();
let backend = Backend::global(cx);
let Some(me) = backend.read(cx).current_user() else {
let (me, client) = {
let backend = backend.read(cx);
(backend.current_user(), backend.client())
};
let Some(me) = me else {
self.refresh.abort();
return;
};
let client = backend.read(cx).client();
let state = self.state.clone();
let work = cx.background_spawn(async move { query_inbox(&client, me, &state).await });
@@ -570,10 +561,6 @@ fn sub_activity(event: &Event, me: Option<PublicKey>, cx: &App) -> AnyElement {
}
fn activity_phrase(kind: Kind) -> &'static str {
if kind == COVER_NOTE_KIND {
return "added a note";
}
match kind {
Kind::GitIssue => "opened an issue",
Kind::GitPullRequest => "opened a PR",
@@ -79,8 +79,7 @@ pub struct PullRequestDetailView {
pane: Entity<DiffPane>,
commit_item_sizes: Rc<Vec<Size<Pixels>>>,
commit_scroll_handle: VirtualListScrollHandle,
/// The dock caches item panels, so without this observer a panel opened
/// before the store loaded would stay on its placeholder.
tasks: Vec<gpui::Task<Result<(), anyhow::Error>>>,
_subscription: Subscription,
}
@@ -124,6 +123,7 @@ impl PullRequestDetailView {
pane,
commit_item_sizes: Rc::new(Vec::new()),
commit_scroll_handle: VirtualListScrollHandle::new(),
tasks: Vec::new(),
_subscription: subscription,
}
}
@@ -327,7 +327,7 @@ impl PullRequestDetailView {
Ok(())
});
task.detach();
self.tasks.push(task);
}
/// Open the diff of `commit_id` in the bottom dock of the area.
@@ -68,6 +68,7 @@ pub struct NewPullRequestView {
pane: Entity<DiffPane>,
scroll_handle: VirtualListScrollHandle,
item_sizes: Rc<Vec<Size<Pixels>>>,
tasks: Vec<gpui::Task<Result<(), anyhow::Error>>>,
_subscriptions: Vec<Subscription>,
}
@@ -323,6 +324,7 @@ impl NewPullRequestView {
pane,
scroll_handle: VirtualListScrollHandle::new(),
item_sizes: Rc::new(Vec::new()),
tasks: Vec::new(),
_subscriptions: subscriptions,
}
}
@@ -390,7 +392,8 @@ impl NewPullRequestView {
Ok(())
});
task.detach();
self.tasks.push(task);
}
/// Branches and the current branch are read off the UI thread, then applied.
@@ -419,7 +422,8 @@ impl NewPullRequestView {
Ok(())
});
task.detach();
self.tasks.push(task);
}
fn apply_checkout(
@@ -636,7 +640,8 @@ impl NewPullRequestView {
Ok(())
});
task.detach();
self.tasks.push(task);
}
#[allow(clippy::too_many_arguments)]
@@ -830,7 +835,7 @@ impl NewPullRequestView {
Ok(())
});
task.detach();
self.tasks.push(task);
}
fn submit(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -911,7 +916,7 @@ impl NewPullRequestView {
Ok(())
});
task.detach();
self.tasks.push(task);
}
fn open_commit_diff(&mut self, commit_id: &str, window: &mut Window, cx: &mut Context<Self>) {
@@ -215,15 +215,19 @@ pub fn load_user_grasp_servers(
cx: &mut App,
) {
let backend = Backend::global(cx);
let Some(user) = backend.read(cx).current_user() else {
let (user, client) = {
let backend = backend.read(cx);
(backend.current_user(), backend.client())
};
let Some(user) = user else {
state.update(cx, |state, _| state.loading_servers = false);
return;
};
let client = backend.read(cx).client();
let handle = window.window_handle();
cx.spawn(async move |cx| {
let result = signed_state::user_grasp_list_servers(client, user).await;
let result = signed_state::user_grasp_list_servers(&client, user).await;
let _ = cx.update_window(handle, |_, _window, cx| {
state.update(cx, |state, _| {
+3 -9
View File
@@ -79,16 +79,12 @@ impl SidebarPanel {
let signer_changed = matches!(event, BackendEvent::SignerChanged);
let signer_required = matches!(event, BackendEvent::SignerRequired);
if !signer_changed && !signer_required {
return;
}
if signer_required {
this.banner = pick_banner();
cx.notify();
}
if this.refresh(cx) || signer_required {
if this.refresh(cx) || signer_changed {
cx.notify();
}
}));
@@ -131,16 +127,14 @@ impl SidebarPanel {
let user = backend.read(cx).current_user();
let (announcements, local_repos, scanning) = {
let repo_list = RepoListStore::global(cx);
let repo_list = repo_list.read(cx);
let repo_list = RepoListStore::global(cx).read(cx);
let announcements = user
.as_ref()
.map(|user| repo_list.announcements_of(user))
.unwrap_or_default();
let local = LocalReposStore::global(cx);
let local = local.read(cx);
let local = LocalReposStore::global(cx).read(cx);
let local_repos =
resolve_local_repos(&local.repos, &repo_list.announcements, &announcements);
@@ -18,7 +18,7 @@ use gpui_component::{
ActiveTheme, IconName, IndexPath, Sizable, Theme, ThemeMode, ThemeRegistry, WindowExt, h_flex,
v_flex,
};
use settings::{AppearanceMode, Settings, SettingsStore};
use settings::{AppearanceMode, EventFetchingStrategy, Settings, SettingsStore};
use signed_ui::{SelectOption, setting_block, setting_row};
use super::{normalize_server, server_host};
@@ -52,6 +52,7 @@ fn theme_options(cx: &App) -> (Vec<SelectOption>, Vec<SelectOption>) {
/// Created once when the dialog opens, so control state survives re-renders.
struct SettingsControls {
appearance: Entity<SelectState<Vec<SelectOption>>>,
event_fetching: Entity<SelectState<Vec<SelectOption>>>,
light_theme: Entity<SelectState<Vec<SelectOption>>>,
dark_theme: Entity<SelectState<Vec<SelectOption>>>,
font_size: Entity<InputState>,
@@ -89,6 +90,23 @@ impl SettingsControls {
)
});
let event_fetching_options = vec![
SelectOption::new("curated", "Curated"),
SelectOption::new("uncensored", "Uncensored"),
];
let event_fetching_value = match settings.event_fetching {
EventFetchingStrategy::Curated => "curated",
EventFetchingStrategy::Uncensored => "uncensored",
};
let event_fetching = cx.new(|cx| {
SelectState::new(
event_fetching_options.clone(),
selected_index(&event_fetching_options, event_fetching_value),
window,
cx,
)
});
let (light_options, dark_options) = theme_options(cx);
let light_theme = cx.new(|cx| {
SelectState::new(
@@ -147,6 +165,19 @@ impl SettingsControls {
}
}));
subscriptions.push(cx.subscribe(&event_fetching, |_, event, cx| {
if let SelectEvent::Confirm(Some(value)) = event {
let strategy = match value.as_ref() {
"curated" => EventFetchingStrategy::Curated,
_ => EventFetchingStrategy::Uncensored,
};
let store = SettingsStore::global(cx);
store.update(cx, |store, cx| {
store.edit(|settings| settings.event_fetching = strategy, cx);
});
}
}));
subscriptions.push(cx.subscribe(&light_theme, |_, event, cx| {
if let SelectEvent::Confirm(Some(value)) = event {
let store = SettingsStore::global(cx);
@@ -252,6 +283,7 @@ impl SettingsControls {
Self {
appearance,
event_fetching,
light_theme,
dark_theme,
font_size,
@@ -294,6 +326,8 @@ fn settings_view(controls: &SettingsControls, cx: &mut App) -> impl IntoElement
.child(Separator::horizontal())
.child(grasp_servers_section(&settings, controls, cx))
.child(Separator::horizontal())
.child(event_fetching_section(controls, cx))
.child(Separator::horizontal())
.child(repositories_section(&settings, controls, cx))
}
@@ -306,6 +340,16 @@ fn appearance_section(controls: &SettingsControls, cx: &App) -> impl IntoElement
))
}
/// Which relays repository activity is fetched from.
fn event_fetching_section(controls: &SettingsControls, cx: &App) -> impl IntoElement {
v_flex().w_full().gap_3().child(setting_row(
cx,
"Event Fetching Strategy",
"Curated fetches events from relays in the repository's announcement. Uncensored also fetches from every maintainer's relays.",
Select::new(&controls.event_fetching).w_full(),
))
}
fn theme_section(settings: &Settings, controls: &SettingsControls, cx: &App) -> impl IntoElement {
v_flex()
.gap_3()