refactor 2
This commit is contained in:
@@ -393,16 +393,6 @@ impl InboxView {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(announcement) = RepoListStore::global(cx)
|
||||
.read(cx)
|
||||
.announcements
|
||||
.iter()
|
||||
.find(|announcement| announcement.addr() == address)
|
||||
.cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let item = match kind {
|
||||
Some(Kind::GitIssue) => RepoItem::Issue(root),
|
||||
Some(Kind::GitPullRequest) => RepoItem::PullRequest(root),
|
||||
@@ -410,7 +400,7 @@ impl InboxView {
|
||||
_ => return,
|
||||
};
|
||||
|
||||
open_repo_item(&self.dock_area, &announcement, item, window, cx);
|
||||
open_repo_item(&self.dock_area, &address, None, item, window, cx);
|
||||
}
|
||||
|
||||
fn render_entry(&self, ix: usize, cx: &Context<Self>) -> AnyElement {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Error;
|
||||
use dock::{DockArea, add_center_panel, panel_handle};
|
||||
@@ -8,8 +7,8 @@ use gpui::prelude::*;
|
||||
use gpui::{App, Context, Entity, WeakEntity, Window};
|
||||
use gpui_base::dock::PanelView;
|
||||
use nostr::prelude::EventId;
|
||||
use signed_core::{Announcement, filters};
|
||||
use signed_state::{Backend, RepoListStore, RepoStore};
|
||||
use signed_core::{Announcement, RepoAddr};
|
||||
use signed_state::RepoStore;
|
||||
|
||||
use super::RepoDetailView;
|
||||
use crate::views::issues::IssuesView;
|
||||
@@ -114,74 +113,20 @@ impl RepoDetailView {
|
||||
}
|
||||
|
||||
/// Open the upstream repository, the `u` tag of this fork's announcement.
|
||||
/// The upstream announcement may not be in the local database yet.
|
||||
/// Subscribe for it and open the panel as soon as it lands.
|
||||
///
|
||||
/// The announcement may not be in the local database yet. The panel opens
|
||||
/// from the address and fills in when the store loads it; the store's
|
||||
/// `subscribe_remote` fetches it from the bootstrap relays.
|
||||
pub(super) fn open_upstream(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.pending_upstream.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(announcement) = self.announcement(cx).cloned() else {
|
||||
let Some(addr) = self
|
||||
.announcement(cx)
|
||||
.and_then(|announcement| announcement.upstream.as_ref())
|
||||
.and_then(|upstream| upstream.addr.clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(addr) = announcement.upstream.and_then(|upstream| upstream.addr) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(found) = RepoListStore::global(cx)
|
||||
.read(cx)
|
||||
.announcements
|
||||
.iter()
|
||||
.find(|a| a.addr() == addr)
|
||||
.cloned()
|
||||
{
|
||||
open_repo_panel(&self.dock_area, &found, window, &mut *cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let backend = Backend::global(cx);
|
||||
backend.update(cx, |backend, cx| {
|
||||
backend.subscribe_bootstrap(vec![filters::announcement(&addr)], cx);
|
||||
});
|
||||
self.pending_upstream = Some(addr);
|
||||
|
||||
let task: gpui::Task<Result<(), Error>> = cx.spawn_in(window, async move |this, cx| {
|
||||
for _ in 0..60 {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(250))
|
||||
.await;
|
||||
|
||||
let opened = this.update_in(cx, |this, window, cx| {
|
||||
let Some(addr) = this.pending_upstream.clone() else {
|
||||
return true;
|
||||
};
|
||||
let found = RepoListStore::global(cx)
|
||||
.read(cx)
|
||||
.announcements
|
||||
.iter()
|
||||
.find(|a| a.addr() == addr)
|
||||
.cloned();
|
||||
match found {
|
||||
Some(found) => {
|
||||
this.pending_upstream = None;
|
||||
open_repo_panel(&this.dock_area, &found, window, &mut *cx);
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
})?;
|
||||
|
||||
if opened {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
this.update(cx, |this, _cx| this.pending_upstream = None)?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
task.detach();
|
||||
open_repo_panel(&self.dock_area, &addr, None, window, &mut *cx);
|
||||
}
|
||||
|
||||
/// Open the dialog guiding the user through publishing the local repository to NIP-34.
|
||||
@@ -194,15 +139,21 @@ impl RepoDetailView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Open `announcement` as a repository panel in the dock's center.
|
||||
/// Open `addr`'s repository as a panel in the dock's center.
|
||||
///
|
||||
/// `hint` is an announcement already in hand for `addr`. It seeds the store's
|
||||
/// relays and lets the explorer load without waiting for the database; the
|
||||
/// store loads the announcement itself when the hint is absent, so an entry
|
||||
/// point with only an address works too.
|
||||
pub(crate) fn open_repo_panel(
|
||||
dock_area: &WeakEntity<DockArea>,
|
||||
announcement: &Announcement,
|
||||
addr: &RepoAddr,
|
||||
hint: Option<&Announcement>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<RepoDetailView> {
|
||||
let detail =
|
||||
cx.new(|cx| RepoDetailView::new(dock_area.clone(), announcement.clone(), window, cx));
|
||||
let detail = cx
|
||||
.new(|cx| RepoDetailView::new(dock_area.clone(), addr.clone(), hint.cloned(), window, cx));
|
||||
|
||||
if let Some(dock_area) = dock_area.upgrade() {
|
||||
dock_area.update(cx, |dock_area, cx| {
|
||||
@@ -213,9 +164,16 @@ pub(crate) fn open_repo_panel(
|
||||
detail
|
||||
}
|
||||
|
||||
/// The nostr store of `announcement`'s repository, without opening a repository panel.
|
||||
fn repo_store(announcement: &Announcement, cx: &mut App) -> Entity<RepoStore> {
|
||||
cx.new(|cx| RepoStore::new(announcement.addr(), announcement.relays.clone(), cx))
|
||||
/// The nostr store of `addr`'s repository, without opening a repository panel.
|
||||
///
|
||||
/// `hint` is an announcement already in hand for `addr`. It only seeds the
|
||||
/// relays to connect to right away; the store loads the announcement from the
|
||||
/// local database on its first pass, so the hint is optional.
|
||||
fn repo_store(addr: &RepoAddr, hint: Option<&Announcement>, cx: &mut App) -> Entity<RepoStore> {
|
||||
let relays = hint
|
||||
.map(|announcement| announcement.relays.clone())
|
||||
.unwrap_or_default();
|
||||
cx.new(|cx| RepoStore::new(addr.clone(), relays, cx))
|
||||
}
|
||||
|
||||
/// An item of a repository to open from outside its detail panel.
|
||||
@@ -226,16 +184,21 @@ pub(crate) enum RepoItem {
|
||||
Patch,
|
||||
}
|
||||
|
||||
/// Open the detail panel of `item` in `announcement`'s repository, in the dock's center.
|
||||
/// Open the detail panel of `item` in `addr`'s repository, in the dock's center.
|
||||
///
|
||||
/// The repository store is built here, not taken from a `RepoDetailView`, so the
|
||||
/// item panel is the only panel docked.
|
||||
///
|
||||
/// `hint` is an announcement already in hand for `addr`, e.g. the inbox row the
|
||||
/// item was clicked from. The store resolves the repository from the local
|
||||
/// database on its own, so an entry point with only the address works too.
|
||||
///
|
||||
/// A patch opens nothing: patches are only consumed inside a pull request's
|
||||
/// detail panel, and have no panel of their own.
|
||||
pub(crate) fn open_repo_item(
|
||||
dock_area: &WeakEntity<DockArea>,
|
||||
announcement: &Announcement,
|
||||
addr: &RepoAddr,
|
||||
hint: Option<&Announcement>,
|
||||
item: RepoItem,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
@@ -243,11 +206,11 @@ pub(crate) fn open_repo_item(
|
||||
let panel: Arc<dyn PanelView> =
|
||||
match item {
|
||||
RepoItem::Issue(issue_id) => {
|
||||
let store = repo_store(announcement, cx);
|
||||
let store = repo_store(addr, hint, cx);
|
||||
panel_handle(cx.new(|cx| IssueDetailView::new(store, issue_id, window, cx)))
|
||||
}
|
||||
RepoItem::PullRequest(pr_id) => {
|
||||
let store = repo_store(announcement, cx);
|
||||
let store = repo_store(addr, hint, cx);
|
||||
panel_handle(cx.new(|cx| {
|
||||
PullRequestDetailView::new(dock_area.clone(), store, pr_id, window, cx)
|
||||
}))
|
||||
|
||||
@@ -190,7 +190,7 @@ fn init_repository(
|
||||
window.close_dialog(cx);
|
||||
if let Some(view) = view.upgrade() {
|
||||
view.update(cx, |this, cx| {
|
||||
this.apply_announcement(announcement, cx);
|
||||
this.apply_announcement(announcement, window, cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
@@ -73,7 +73,8 @@ pub struct RepoDetailView {
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
/// Snapshot taken at open time.
|
||||
///
|
||||
/// `None` for local repositories that haven't been published yet.
|
||||
/// `None` for local repositories that haven't been published yet, and for a
|
||||
/// repository opened by address until its store loads the announcement.
|
||||
initial: Option<Announcement>,
|
||||
/// Per-repository nostr store, holding announcement, issues, PRs and statuses.
|
||||
///
|
||||
@@ -162,37 +163,32 @@ pub struct RepoDetailView {
|
||||
/// The global checkouts store's ready-to-push statuses of this repository,
|
||||
/// last seen when they drove a render.
|
||||
push_statuses: Vec<CheckoutStatus>,
|
||||
/// Upstream repository, from this fork's `u` tag, the user asked to open.
|
||||
/// Its announcement is still being fetched.
|
||||
pending_upstream: Option<RepoAddr>,
|
||||
}
|
||||
|
||||
impl RepoDetailView {
|
||||
/// Open a repository announced.
|
||||
/// Open a repository by address.
|
||||
///
|
||||
/// The store connects to the announcement's relays and loads issues, PRs and statuses.
|
||||
/// `hint` is an announcement already in hand. It seeds the store's relays
|
||||
/// and the explorer's clone URLs; without it the panel waits for the store
|
||||
/// to load the announcement from the local database.
|
||||
pub fn new(
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
initial: Announcement,
|
||||
addr: RepoAddr,
|
||||
hint: Option<Announcement>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
// The announcement we opened from already carries the NIP-34 `relays` tag.
|
||||
//
|
||||
// The store connects to those relays immediately, no bootstrap fetch wait.
|
||||
let addr = initial.addr();
|
||||
let relays = initial.relays.clone();
|
||||
let relays = hint
|
||||
.as_ref()
|
||||
.map(|announcement| announcement.relays.clone())
|
||||
.unwrap_or_default();
|
||||
let store = cx.new(|cx| RepoStore::new(addr, relays, cx));
|
||||
|
||||
let mut view = Self::new_common(
|
||||
dock_area,
|
||||
Some(initial),
|
||||
Some(store.clone()),
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
view.attach_store(&store, cx);
|
||||
let mut view = Self::new_common(dock_area, hint, Some(store.clone()), None, window, cx);
|
||||
view.attach_store(&store, window, cx);
|
||||
view
|
||||
}
|
||||
|
||||
@@ -313,7 +309,6 @@ impl RepoDetailView {
|
||||
ready_head: None,
|
||||
ready_statuses: Vec::new(),
|
||||
push_statuses: Vec::new(),
|
||||
pending_upstream: None,
|
||||
focus_handle: cx.focus_handle(),
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use gpui::prelude::*;
|
||||
use gpui::{Context, Entity};
|
||||
use gpui::{Context, Entity, Window};
|
||||
use signed_core::Announcement;
|
||||
use signed_state::{Backend, CheckoutsStore, LocalReposStore, RepoStore};
|
||||
|
||||
@@ -13,6 +13,7 @@ impl RepoDetailView {
|
||||
pub(crate) fn apply_announcement(
|
||||
&mut self,
|
||||
announcement: Announcement,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
// The repository is no longer a bare local repo.
|
||||
@@ -24,7 +25,7 @@ impl RepoDetailView {
|
||||
cx.new(|cx| RepoStore::new(announcement.addr(), announcement.relays.clone(), cx));
|
||||
// Re-render on store refreshes, issues, PRs and statuses.
|
||||
// Keep the ready-to-contribute statuses of this repository requested.
|
||||
self.attach_store(&store, cx);
|
||||
self.attach_store(&store, window, cx);
|
||||
self.store = Some(store);
|
||||
self.initial = Some(announcement);
|
||||
cx.notify();
|
||||
@@ -32,11 +33,27 @@ impl RepoDetailView {
|
||||
|
||||
/// Observe the repository's store, re-render on refreshes.
|
||||
/// Request the ready-to-contribute statuses for it.
|
||||
pub(super) fn attach_store(&mut self, store: &Entity<RepoStore>, cx: &mut Context<Self>) {
|
||||
pub(super) fn attach_store(
|
||||
&mut self,
|
||||
store: &Entity<RepoStore>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self._subscriptions
|
||||
.push(cx.observe(store, |this, _store, cx| {
|
||||
log::debug!("repo detail: store notify");
|
||||
.push(cx.observe_in(store, window, |this, store, window, cx| {
|
||||
this.refresh_ready_statuses(cx);
|
||||
|
||||
// A repository opened from its address alone starts without an
|
||||
// announcement. Adopt the store's first one so the explorer can
|
||||
// load; later passes leave the snapshot and the selection alone.
|
||||
let announcement = store.read(cx).announcement.clone();
|
||||
if this.initial.is_none()
|
||||
&& let Some(announcement) = announcement
|
||||
{
|
||||
this.initial = Some(announcement);
|
||||
this.load_repo(window, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}));
|
||||
self.refresh_ready_statuses(cx);
|
||||
|
||||
@@ -189,7 +189,13 @@ impl RepoListView {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
open_repo_panel(&self.dock_area, announcement, window, cx);
|
||||
open_repo_panel(
|
||||
&self.dock_area,
|
||||
&announcement.addr(),
|
||||
Some(announcement),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn render_card(
|
||||
|
||||
@@ -257,5 +257,11 @@ fn open_repo(
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
open_repo_panel(&dock_area, &announcement, window, cx);
|
||||
open_repo_panel(
|
||||
&dock_area,
|
||||
&announcement.addr(),
|
||||
Some(&announcement),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -260,7 +260,13 @@ impl SidebarPanel {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
open_repo_panel(&self.dock_area, announcement, window, &mut *cx);
|
||||
open_repo_panel(
|
||||
&self.dock_area,
|
||||
&announcement.addr(),
|
||||
Some(announcement),
|
||||
window,
|
||||
&mut *cx,
|
||||
);
|
||||
}
|
||||
|
||||
/// Open a local repository's detail view in the dock's center.
|
||||
|
||||
+24
-11
@@ -1,6 +1,6 @@
|
||||
# Repository state and panel flow plan
|
||||
|
||||
Status: proposed (2026-09-13)
|
||||
Status: phases 1-2 implemented, phase 3 next (2026-09-13)
|
||||
|
||||
Builds on `docs/backend-rearchitecture.md`, especially §7 (notify audit),
|
||||
§11 (split independently-observed state), §12 (one debounce at the source)
|
||||
@@ -237,10 +237,8 @@ flow for announced repositories.
|
||||
- `open_repo_item(dock_area, addr: &RepoAddr, item, window, cx)`.
|
||||
- Inbox passes its already-parsed `address` (`signed_core::InboxItem.address`,
|
||||
the root event's `a` tag) and drops the `RepoListStore` lookup.
|
||||
- `open_upstream` drops the 60 x 250 ms wait loop: the store's
|
||||
`repo_filters` already include the announcement filter and
|
||||
`subscribe_remote` runs on creation, so the panel opens immediately and
|
||||
fills in.
|
||||
- `open_upstream` no longer polls the list; it opens the panel by address and
|
||||
the store fills it in (Phase 2).
|
||||
- `RepoItem::Patch` behavior is unchanged.
|
||||
|
||||
## Phases
|
||||
@@ -271,17 +269,32 @@ Status: implemented, except step 6.
|
||||
|
||||
### Phase 2 - entry points by identity
|
||||
|
||||
1. `views/repo/actions.rs`: `repo_store(addr, hint, cx)`;
|
||||
`open_repo_item(addr, ...)`; `open_upstream` opens directly.
|
||||
2. `views/inbox.rs`: pass `address`, delete the announcement lookup and its
|
||||
silent early-return.
|
||||
Status: implemented.
|
||||
|
||||
1. `views/repo/actions.rs`: `repo_store(addr, hint, cx)` seeds the store's
|
||||
relays from an optional hint but needs nothing else; the store loads the
|
||||
announcement itself. `open_repo_item(addr, hint, item, window, cx)` takes
|
||||
the address, not a hydrated announcement.
|
||||
2. `views/inbox.rs`: `open` passes its already-parsed `address` and the
|
||||
`RepoListStore` lookup with its silent early-return is gone. An inbox row
|
||||
opens whether or not the repository is in the list yet.
|
||||
3. `open_repo_panel` and `RepoDetailView::new` take an address plus an optional
|
||||
hint, so a repository panel opens from a `RepoAddr` alone. This pulls the
|
||||
address-based constructor forward from Phase 3 step 2.
|
||||
4. `RepoDetailView::attach_store` adopts the store's first announcement when
|
||||
`initial` is still empty and calls `load_repo`, so a panel opened by address
|
||||
fills in instead of waiting for the caller to have the announcement.
|
||||
5. `open_upstream`: the 60 x 250 ms poll and the `pending_upstream` field are
|
||||
gone. It opens the panel by address; the store's `subscribe_remote` fetches
|
||||
the announcement from the bootstrap relays and step 4 loads the explorer.
|
||||
|
||||
### Phase 3 - one entity for local and NIP-34
|
||||
|
||||
1. `signed_state/src/repo.rs`: `addr`/`path` options, `new_local`,
|
||||
`announce`, `Option<Subscription>`, action guards.
|
||||
2. `views/repo/mod.rs`: single `store` field; `new`/`new_local`; header,
|
||||
display name, `load_repo`, `open_init_dialog` derive from the store.
|
||||
2. `views/repo/mod.rs`: single `store` field; `new_local`; header,
|
||||
display name, `load_repo`, `open_init_dialog` derive from the store. The
|
||||
address-based `new` is already in place from Phase 2.
|
||||
3. `views/repo/store.rs`: always observe; `refresh_statuses` returns false
|
||||
when not announced.
|
||||
4. `views/repo/actions.rs`, `header.rs`, `banners.rs`: drop
|
||||
|
||||
Reference in New Issue
Block a user