fix: date repository cards from state events, not announcements
The explore list fell back to the announcement created_at because kind 30618 state events were only fetched per repository, on open. Sync them globally and refresh the list on published state events, so a card shows the last push without the user cloning first. Open repository panels now also react to state events from relays and to locally published state events.
This commit is contained in:
@@ -184,6 +184,11 @@ pub fn all_announcements() -> Filter {
|
|||||||
Filter::new().kind(Kind::GitRepoAnnouncement)
|
Filter::new().kind(Kind::GitRepoAnnouncement)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// All repository state events, carrying each repository's refs and last push time.
|
||||||
|
pub fn all_states() -> Filter {
|
||||||
|
Filter::new().kind(Kind::RepoState)
|
||||||
|
}
|
||||||
|
|
||||||
/// How far back deletion requests are fetched and stored.
|
/// How far back deletion requests are fetched and stored.
|
||||||
const DELETIONS_LOOKBACK: Duration = Duration::from_secs(3 * 365 * 86_400);
|
const DELETIONS_LOOKBACK: Duration = Duration::from_secs(3 * 365 * 86_400);
|
||||||
|
|
||||||
|
|||||||
@@ -207,7 +207,11 @@ impl RepoStore {
|
|||||||
update.kind == Kind::EventDeletion || update.kind == Kind::RequestToVanish;
|
update.kind == Kind::EventDeletion || update.kind == Kind::RequestToVanish;
|
||||||
let coordinate = update.coordinate.as_ref() == Some(addr);
|
let coordinate = update.coordinate.as_ref() == Some(addr);
|
||||||
let author = update.author == addr.public_key;
|
let author = update.author == addr.public_key;
|
||||||
let kind = update.kind == Kind::GitRepoAnnouncement;
|
// Announcements and state share the owner and identifier.
|
||||||
|
// State events carry `d`, not `a`, so `coordinate` misses them.
|
||||||
|
let authored = (update.kind == Kind::GitRepoAnnouncement
|
||||||
|
|| update.kind == Kind::RepoState)
|
||||||
|
&& author;
|
||||||
// NIP-22 comments carry no `a` tag.
|
// NIP-22 comments carry no `a` tag.
|
||||||
// Coordinate matching fails for them.
|
// Coordinate matching fails for them.
|
||||||
// Any comment may reference this repository's roots.
|
// Any comment may reference this repository's roots.
|
||||||
@@ -216,18 +220,22 @@ impl RepoStore {
|
|||||||
// Any status event may reference a root of this repository.
|
// Any status event may reference a root of this repository.
|
||||||
let status = RepoStatus::from_kind(update.kind).is_some();
|
let status = RepoStatus::from_kind(update.kind).is_some();
|
||||||
|
|
||||||
deletion || coordinate || (author && kind) || comment || status
|
deletion || coordinate || authored || comment || status
|
||||||
}),
|
}),
|
||||||
BackendEvent::Published(event) => {
|
BackendEvent::Published(event) => {
|
||||||
let kind = event.kind == Kind::GitRepoAnnouncement;
|
let announcement = event.kind == Kind::GitRepoAnnouncement;
|
||||||
let author = event.pubkey == addr.public_key;
|
let author = event.pubkey == addr.public_key;
|
||||||
let coordinate = event.tags.coordinates().into_iter().any(|c| c == *addr);
|
let coordinate = event.tags.coordinates().into_iter().any(|c| c == *addr);
|
||||||
|
// State events carry their repository id in the `d` tag.
|
||||||
|
let state = event.kind == Kind::RepoState
|
||||||
|
&& author
|
||||||
|
&& event.tags.identifier().as_deref() == Some(addr.identifier.as_str());
|
||||||
// Locally published deletions may target any event of this repository.
|
// Locally published deletions may target any event of this repository.
|
||||||
// Refresh so they take effect immediately, like relay deletions.
|
// Refresh so they take effect immediately, like relay deletions.
|
||||||
let deletion =
|
let deletion =
|
||||||
event.kind == Kind::EventDeletion || event.kind == Kind::RequestToVanish;
|
event.kind == Kind::EventDeletion || event.kind == Kind::RequestToVanish;
|
||||||
|
|
||||||
coordinate || (kind && author) || deletion
|
coordinate || (announcement && author) || state || deletion
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -83,12 +83,16 @@ impl RepoListStore {
|
|||||||
BackendEvent::Published(event) => {
|
BackendEvent::Published(event) => {
|
||||||
let announcement = event.kind == Kind::GitRepoAnnouncement;
|
let announcement = event.kind == Kind::GitRepoAnnouncement;
|
||||||
|
|
||||||
|
// Locally published state events advance last activity.
|
||||||
|
// Refresh so a push made in this session dates the card immediately.
|
||||||
|
let state = event.kind == Kind::RepoState;
|
||||||
|
|
||||||
// Locally published deletions are already in the local database.
|
// Locally published deletions are already in the local database.
|
||||||
// Refresh so they take effect immediately, like relay deletions.
|
// Refresh so they take effect immediately, like relay deletions.
|
||||||
let deletion =
|
let deletion =
|
||||||
event.kind == Kind::EventDeletion || event.kind == Kind::RequestToVanish;
|
event.kind == Kind::EventDeletion || event.kind == Kind::RequestToVanish;
|
||||||
|
|
||||||
announcement || deletion
|
announcement || state || deletion
|
||||||
}
|
}
|
||||||
// Only a completed sync refreshes the list.
|
// Only a completed sync refreshes the list.
|
||||||
// Progress ticks would re-scan the whole database several times
|
// Progress ticks would re-scan the whole database several times
|
||||||
@@ -133,6 +137,9 @@ impl RepoListStore {
|
|||||||
|
|
||||||
backend.update(cx, |backend, cx| {
|
backend.update(cx, |backend, cx| {
|
||||||
backend.sync_bootstrap(filters::all_announcements(), cx);
|
backend.sync_bootstrap(filters::all_announcements(), cx);
|
||||||
|
// State events carry the last push time, so syncing them globally
|
||||||
|
// lets the list date every repository without the user opening it.
|
||||||
|
backend.sync_bootstrap(filters::all_states(), cx);
|
||||||
// Deletion requests, NIP-09/62, must be known before any announcement is shown.
|
// Deletion requests, NIP-09/62, must be known before any announcement is shown.
|
||||||
backend.sync_bootstrap(filters::deletions(), cx);
|
backend.sync_bootstrap(filters::deletions(), cx);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -123,24 +123,26 @@ flowchart TD
|
|||||||
|
|
||||||
## Remediation options
|
## Remediation options
|
||||||
|
|
||||||
- **A. Global state sync (smallest change).**
|
- **A. Global state sync (smallest change). — implemented.**
|
||||||
Add `sync_bootstrap(Filter::new().kind(Kind::RepoState))` alongside the announcement
|
`RepoListStore::subscribe_remote` now also syncs `filters::all_states()`
|
||||||
sync in `RepoListStore::subscribe_remote`. Kind `30618` is addressable, so relays
|
(`Filter::new().kind(Kind::RepoState)`) alongside the announcement sync. Kind `30618`
|
||||||
keep one event per repository — volume comparable to announcements. Caveat: coverage
|
is addressable, so relays keep one event per repository — volume comparable to
|
||||||
depends on whether state events reach the bootstrap relays; if a grasp server is the
|
announcements. Caveat: coverage depends on whether state events reach the bootstrap
|
||||||
only holder, those repositories remain unseen.
|
relays; if a grasp server is the only holder, those repositories remain unseen.
|
||||||
|
|
||||||
- **B. Targeted fetch for the user's own repositories.**
|
- **B. Targeted fetch for the user's own repositories. — not implemented.**
|
||||||
In `sync_inbox` (`crates/signed_state/src/backend.rs` L1126-1152), where the user's
|
In `sync_inbox` (`crates/signed_state/src/backend.rs` L1126-1152), where the user's
|
||||||
own repositories' relays are already connected, also sync `filters::state(addr)` per
|
own repositories' relays are already connected, also sync `filters::state(addr)` per
|
||||||
own repository. This directly addresses "my repositories updated elsewhere" at low
|
own repository. This directly addresses "my repositories updated elsewhere" at low
|
||||||
cost. Note the existing race: `sync_inbox` reads the announcement list right after
|
cost. Note the existing race: `sync_inbox` reads the announcement list right after
|
||||||
login, which may not be loaded yet.
|
login, which may not be loaded yet.
|
||||||
|
|
||||||
- **C. Reactivity fixes.**
|
- **C. Reactivity fixes. — implemented.**
|
||||||
Include `Kind::RepoState` in the `Published` refresh conditions, and in
|
`Kind::RepoState` is now included in the `BackendEvent::Published` refresh condition of
|
||||||
`RepoStore::subscribe_backend` either match kind plus author, or extend `Update` to
|
both `RepoListStore` and `RepoStore`. `RepoStore::subscribe_backend` also matches relay
|
||||||
carry the `d`-tag identifier.
|
state updates by owner plus kind — state events carry `d`, not `a`, so coordinate
|
||||||
|
matching misses them — and locally published state updates by owner plus the `d`
|
||||||
|
identifier.
|
||||||
|
|
||||||
Suggested combination: A + C, plus B if own-repository freshness on fresh profiles
|
A + C are implemented; B remains available if own-repository freshness on fresh profiles
|
||||||
matters.
|
matters.
|
||||||
|
|||||||
Reference in New Issue
Block a user