fix: date repositories from their state #23

Merged
reya merged 4 commits from fix/repo-updated-timestamp into master 2026-09-25 13:13:37 +00:00
4 changed files with 39 additions and 17 deletions
Showing only changes of commit 42c1229a90 - Show all commits
+5
View File
@@ -184,6 +184,11 @@ pub fn all_announcements() -> Filter {
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.
const DELETIONS_LOOKBACK: Duration = Duration::from_secs(3 * 365 * 86_400);
+12 -4
View File
@@ -207,7 +207,11 @@ impl RepoStore {
update.kind == Kind::EventDeletion || update.kind == Kind::RequestToVanish;
let coordinate = update.coordinate.as_ref() == Some(addr);
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.
// Coordinate matching fails for them.
// Any comment may reference this repository's roots.
@@ -216,18 +220,22 @@ impl RepoStore {
// Any status event may reference a root of this repository.
let status = RepoStatus::from_kind(update.kind).is_some();
deletion || coordinate || (author && kind) || comment || status
deletion || coordinate || authored || comment || status
}),
BackendEvent::Published(event) => {
let kind = event.kind == Kind::GitRepoAnnouncement;
let announcement = event.kind == Kind::GitRepoAnnouncement;
let author = event.pubkey == addr.public_key;
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.
// Refresh so they take effect immediately, like relay deletions.
let deletion =
event.kind == Kind::EventDeletion || event.kind == Kind::RequestToVanish;
coordinate || (kind && author) || deletion
coordinate || (announcement && author) || state || deletion
}
_ => false,
};
+8 -1
View File
@@ -83,12 +83,16 @@ impl RepoListStore {
BackendEvent::Published(event) => {
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.
// Refresh so they take effect immediately, like relay deletions.
let deletion =
event.kind == Kind::EventDeletion || event.kind == Kind::RequestToVanish;
announcement || deletion
announcement || state || deletion
}
// Only a completed sync refreshes the list.
// Progress ticks would re-scan the whole database several times
@@ -133,6 +137,9 @@ impl RepoListStore {
backend.update(cx, |backend, 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.
backend.sync_bootstrap(filters::deletions(), cx);
});
+14 -12
View File
@@ -123,24 +123,26 @@ flowchart TD
## Remediation options
- **A. Global state sync (smallest change).**
Add `sync_bootstrap(Filter::new().kind(Kind::RepoState))` alongside the announcement
sync in `RepoListStore::subscribe_remote`. Kind `30618` is addressable, so relays
keep one event per repository — volume comparable to announcements. Caveat: coverage
depends on whether state events reach the bootstrap relays; if a grasp server is the
only holder, those repositories remain unseen.
- **A. Global state sync (smallest change). — implemented.**
`RepoListStore::subscribe_remote` now also syncs `filters::all_states()`
(`Filter::new().kind(Kind::RepoState)`) alongside the announcement sync. Kind `30618`
is addressable, so relays keep one event per repository — volume comparable to
announcements. Caveat: coverage depends on whether state events reach the bootstrap
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
own repositories' relays are already connected, also sync `filters::state(addr)` per
own repository. This directly addresses "my repositories updated elsewhere" at low
cost. Note the existing race: `sync_inbox` reads the announcement list right after
login, which may not be loaded yet.
- **C. Reactivity fixes.**
Include `Kind::RepoState` in the `Published` refresh conditions, and in
`RepoStore::subscribe_backend` either match kind plus author, or extend `Update` to
carry the `d`-tag identifier.
- **C. Reactivity fixes. — implemented.**
`Kind::RepoState` is now included in the `BackendEvent::Published` refresh condition of
both `RepoListStore` and `RepoStore`. `RepoStore::subscribe_backend` also matches relay
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.