chore: bind sync status to per-view directories (#32)
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: #32
This commit was merged in pull request #32.
This commit is contained in:
2026-10-16 02:40:20 +00:00
parent 30ff255372
commit 946128433b
5 changed files with 280 additions and 162 deletions
+18 -9
View File
@@ -27,7 +27,7 @@ use signed_core::{Announcement, RepoAddr, RepoStatus};
use signed_git::{FileCommit, GitCache, RefSync, Repo};
use signed_state::{
Backend, CheckoutStatus, CheckoutSyncStatus, CheckoutsStore, LocalReposStore, Mirrors,
Nip34Binding, Nip34Kind, PushStepStatus, RepoListStore, RepoStore, SyncStatusStore,
Nip34Binding, Nip34Kind, PushStepStatus, RepoListStore, RepoStore, SyncStatusDirectory,
};
use signed_ui::{
Avatar, CountBadge, DropdownButton, copy_row, menu_copy_row, ref_selector_trigger,
@@ -96,6 +96,8 @@ pub struct RepoDetailView {
tasks: Vec<Task<Result<(), Error>>>,
/// Profiles rendered by the header, maintainers and tab title.
directory: Entity<ProfileDirectory>,
/// Sync status feeding the sync-to-Nostr banner.
sync_directory: Entity<SyncStatusDirectory>,
_subscriptions: Vec<Subscription>,
}
@@ -156,7 +158,6 @@ impl RepoDetailView {
cx: &mut Context<Self>,
) -> Self {
let checkouts = CheckoutsStore::global(cx);
let sync_status = SyncStatusStore::global(cx);
let files = cx.new(RepoFilesView::new);
let history = cx.new(|_cx| RepoHistoryView::new(store.clone(), dock_area.clone()));
let refs = RefSwitcher::new(window, cx);
@@ -193,11 +194,6 @@ impl RepoDetailView {
}
}));
// The sync-to-Nostr banner reads the global sync store directly.
subscriptions.push(cx.observe(&sync_status, |_this, _sync, cx| {
cx.notify();
}));
// Defer loading the repository until the window is ready.
cx.defer_in(window, |this, window, cx| {
this.load_repo(window, cx);
@@ -219,6 +215,7 @@ impl RepoDetailView {
banners: Banners::default(),
focus_handle: cx.focus_handle(),
directory: ProfileDirectory::new(cx),
sync_directory: SyncStatusDirectory::new(cx),
_subscriptions: subscriptions,
};
@@ -255,6 +252,7 @@ impl RepoDetailView {
.push(cx.observe_in(store, window, |this, store, window, cx| {
this.refresh_ready_statuses(cx);
this.track_profiles(cx);
this.track_sync_status(cx);
if !this.repo_started && store.read(cx).announcement.is_some() {
this.load_repo(window, cx);
}
@@ -292,6 +290,17 @@ impl RepoDetailView {
});
}
/// Track the repository address so the sync banner stays current.
fn track_sync_status(&mut self, cx: &mut Context<Self>) {
let Some(addr) = self.store.read(cx).addr().cloned() else {
return;
};
self.sync_directory.update(cx, |directory, cx| {
directory.track([addr], cx);
});
}
fn refresh_ready_statuses(&mut self, cx: &mut Context<Self>) {
let Some(addr) = self.store.read(cx).addr().cloned() else {
return;
@@ -349,6 +358,7 @@ impl RepoDetailView {
self.loading = true;
self.error = None;
self.track_profiles(cx);
self.track_sync_status(cx);
cx.notify();
let (announcement, local_path) = {
@@ -1602,8 +1612,7 @@ impl RepoDetailView {
return None;
}
let sync_status = SyncStatusStore::global(cx);
let statuses = sync_status.read(cx).statuses_of(addr);
let statuses = self.sync_directory.read(cx).statuses_of(addr, cx);
statuses.into_iter().find(|checkout| {
checkout.status.ahead_total > 0 && !self.banners.sync_dismissal(checkout)
+15 -33
View File
@@ -25,7 +25,7 @@ use nostr::prelude::{PublicKey, RelayUrl};
use signed_core::{Announcement, RepoAddr};
use signed_state::{
Backend, BackendEvent, CheckoutsStore, LocalReposStore, Nip34Binding, Nip34Kind, RepoListStore,
ResolvedLocalRepo, SyncStatusStore,
ResolvedLocalRepo, SyncStatusDirectory,
};
use signed_ui::{Avatar, NavItem, title_bar_drag_handlers};
@@ -68,10 +68,10 @@ pub struct SidebarPanel {
local_repos: Arc<Vec<ResolvedLocalRepo>>,
/// Unpushed commit counts per announced repository.
unpushed: HashMap<RepoAddr, usize>,
/// Commits not on Nostr per announced repository.
unsynced: HashMap<RepoAddr, usize>,
/// Profiles rendered by the sidebar.
directory: Entity<ProfileDirectory>,
/// Sync statuses for the announced repositories.
sync_directory: Entity<SyncStatusDirectory>,
_subscriptions: Vec<Subscription>,
}
@@ -81,7 +81,6 @@ impl SidebarPanel {
let repos = RepoListStore::global(cx);
let local = LocalReposStore::global(cx);
let checkouts = CheckoutsStore::global(cx);
let sync_status = SyncStatusStore::global(cx);
let mut subscriptions = Vec::new();
@@ -119,12 +118,6 @@ impl SidebarPanel {
}
}));
subscriptions.push(cx.observe(&sync_status, |this, _sync, cx| {
if this.refresh_unsynced(cx) {
cx.notify();
}
}));
Self {
focus_handle: cx.focus_handle(),
dock_area,
@@ -137,8 +130,8 @@ impl SidebarPanel {
local_repos: Arc::new(Vec::new()),
scanning: false,
unpushed: HashMap::new(),
unsynced: HashMap::new(),
directory: ProfileDirectory::new(cx),
sync_directory: SyncStatusDirectory::new(cx),
_subscriptions: subscriptions,
}
}
@@ -189,10 +182,19 @@ impl SidebarPanel {
directory.track(profiles, cx);
});
let addrs: Vec<RepoAddr> = self
.announcements
.iter()
.map(|announcement| announcement.addr())
.collect();
self.sync_directory.update(cx, |directory, cx| {
directory.track(addrs, cx);
});
if announcements_changed {
self.request_push_watches(cx);
self.unpushed.clear();
self.unsynced.clear();
}
announcements_changed || local_changed || scanning_changed
@@ -218,26 +220,6 @@ impl SidebarPanel {
true
}
fn refresh_unsynced(&mut self, cx: &mut Context<Self>) -> bool {
let sync_status = SyncStatusStore::global(cx);
let mut unsynced = HashMap::with_capacity(self.announcements.len());
for announcement in self.announcements.iter() {
let addr = announcement.addr();
let count = sync_status.read(cx).unsynced(&addr);
if count > 0 {
unsynced.insert(addr, count);
}
}
if unsynced == self.unsynced {
return false;
}
self.unsynced = unsynced;
true
}
fn request_push_watches(&self, cx: &mut Context<Self>) {
let checkouts = CheckoutsStore::global(cx);
checkouts.update(cx, |checkouts, cx| {
@@ -538,7 +520,7 @@ impl SidebarPanel {
let addr = announcement.addr();
let unpushed = self.unpushed.get(&addr).copied().unwrap_or(0);
let unsynced = self.unsynced.get(&addr).copied().unwrap_or(0);
let unsynced = self.sync_directory.read(cx).unsynced(&addr, cx);
let mut row = NavItem::new(format!("repo:{}", announcement.id), name, avatar);