From 7a6877ab0ba4d970be02d3770283223e7dfdd278 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Fri, 25 Sep 2026 20:11:20 +0700 Subject: [PATCH] clean up --- CHANGELOG.md | 1 + crates/signed_state/src/backend.rs | 24 ++--- crates/signed_state/src/repos.rs | 2 - docs/repo-updated-timestamp.md | 147 ----------------------------- 4 files changed, 11 insertions(+), 163 deletions(-) delete mode 100644 docs/repo-updated-timestamp.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 9409808..0e8d330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ ### Fixed - Render every avatar at one consistent size, where a surrounding border had shrunk pictures by two pixels and the pixel avatar ignored an explicit size +- Date repositories from their repository state event, so the explore list and open repository views show the latest push instead of the announcement date ### Removed diff --git a/crates/signed_state/src/backend.rs b/crates/signed_state/src/backend.rs index 362c820..1c461b4 100644 --- a/crates/signed_state/src/backend.rs +++ b/crates/signed_state/src/backend.rs @@ -1229,7 +1229,7 @@ impl Backend { self.sync_progress = Some((0, 0)); cx.notify(); - let progress_task: Task> = cx.spawn(async move |this, cx| { + cx.spawn(async move |this, cx| { let mut last_percent: u64 = 0; while rx.changed().await.is_ok() { @@ -1254,23 +1254,18 @@ impl Backend { } } - Ok(()) - }); - progress_task.detach(); + Ok::<(), anyhow::Error>(()) + }) + .detach(); let sync = cx.background_spawn(async move { let opts = SyncOptions::default().progress(tx); sync_bootstrap_only(&client, filter, opts).await }); - let task: Task> = cx.spawn(async move |this, cx| { + cx.spawn(async move |this, cx| { match sync.await { - Ok(summary) => { - log::debug!( - "sync done: {} received, {} sent", - summary.received.len(), - summary.sent.len() - ); + Ok(_) => { this.update(cx, |this, cx| { this.sync_progress = None; cx.emit(BackendEvent::Synced); @@ -1284,9 +1279,10 @@ impl Backend { })?; } } - Ok(()) - }); - task.detach(); + + Ok::<(), anyhow::Error>(()) + }) + .detach(); } /// Emit [`BackendEvent::Published`] for cross-store invalidation. diff --git a/crates/signed_state/src/repos.rs b/crates/signed_state/src/repos.rs index 0552e43..f13ee91 100644 --- a/crates/signed_state/src/repos.rs +++ b/crates/signed_state/src/repos.rs @@ -135,8 +135,6 @@ 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); diff --git a/docs/repo-updated-timestamp.md b/docs/repo-updated-timestamp.md deleted file mode 100644 index bf868ef..0000000 --- a/docs/repo-updated-timestamp.md +++ /dev/null @@ -1,147 +0,0 @@ -# Repository card "Updated" timestamps are stale until first open - -Feedback under investigation: - -> My repositories that I updated yesterday show up as being updated like 6 months ago, -> but then when I open them and they get cloned that timestamp is fixed, which means -> you're not reading the repository state event, only the date of the repository -> announcement event and the git history? - -## Summary - -The reviewer is essentially right. The backend *has* logic to include kind `30618` -repository state events in the "last activity" timestamp, but that logic only reads -events that are **already present in the local nostr database**. The app never -globally fetches state events for repositories the user has not opened, so for those -the timestamp falls back to the announcement's `created_at` — the repository's -creation date. Opening (and cloning) the repository fetches the state event, which is -why the timestamp corrects itself afterwards. - -The "git history" part of the hypothesis is not accurate for this label: no -git-derived timestamp feeds the repository card. The only git-derived times in the UI -are commit timestamps in commit lists and diffs, which are unrelated. - -This is a data-coverage bug, not a missing computation. The code that aggregates state -events is correct; it is simply starved of input on the explore list. - -## Where the timestamp comes from - -The label is rendered in `crates/workspace/src/views/repo_list.rs` (L209-212): - -```rust -let activity = last_activity - .map(relative_time) - .map(|label| SharedString::from(format!("Updated {label}"))) -``` - -`last_activity` is computed in `crates/signed_state/src/repos.rs`, -`RepoListStore::run_refresh` (L192-235), as a max over three sources: - -| Source | Code | Availability | -|---|---|---| -| `announcement.created_at` (kind 30617) | L195-198 | always; seeded for every listed repo | -| kind 30618 repository state events | L200-213 | only events already in the local database | -| NIP-34 activity (issues, PRs, patches, statuses, comments) | L215-235 | only local database, windowed to 90 days (`ACTIVITY_WINDOW`, L13-14) | - -The critical gap is the global sync scope. `RepoListStore::subscribe_remote` -(`crates/signed_state/src/repos.rs` L131-139) only syncs: - -```rust -backend.sync_bootstrap(filters::all_announcements(), cx); // kind 30617 only -backend.sync_bootstrap(filters::deletions(), cx); -``` - -State events are fetched **per repository, on open**, via `RepoStore`: - -- `repo_filters` (`crates/signed_state/src/repo.rs` L254-270) queries - `[GitRepoAnnouncement, RepoState]` plus activity and deletions. -- `subscribe_remote` (L296-306) runs those filters against the bootstrap relays. -- `connect_announced_relays` (L272-294) runs a negentropy sync of the same filters - against the relays in the announcement's NIP-34 `relays` tag. -- Both fire from `RepoStore::new`'s deferred initialization (L93-112). - -Publishes only ever create a state event, never re-publish the announcement: -`sign_state_event` in `crates/signed_state/src/backend.rs` (L1655-1682) and the -fan-out at L907-918. A repository created 6 months ago and pushed yesterday therefore -has an announcement dated 6 months ago and a state event dated yesterday. - -```mermaid -flowchart TD - A[App start / login] --> B[RepoListStore sync: 30617 announcements + deletions] - B --> C[Explore card: Updated = announcement created_at] - C --> D{Repo opened?} - D -- no --> C - D -- yes --> E[RepoStore fetches 30617 + 30618 from bootstrap + announced relays] - E --> F[State event stored in local DB] - F --> G[BackendEvent NostrUpdate RepoState] - G --> H[RepoListStore refreshes, last_activity recomputed] - H --> I[Card shows push date] -``` - -## Why the report reproduces exactly - -1. The announcement (kind 30617) is addressable and effectively static; pushes replace - only the state event (kind 30618). -2. On a database that has never fetched that repository (fresh install, another device, - or wasm where the database is in-memory per session), only the announcement is - known, so the card shows the creation date. -3. Opening the repository triggers the per-repo fetch; the state event lands in the - database, and `RepoListStore` does react to `RepoState` updates - (`crates/signed_state/src/repos.rs` L69-82), so the card corrects itself. - -## Secondary issues found in the same area - -1. **`RepoStore` ignores live state events.** - `subscribe_backend` (`crates/signed_state/src/repo.rs` L195-239) matches relay - updates via `Update.coordinate`, which is built from `a` tags only - (`crates/signed_nostr/src/update.rs` L13-21). A kind `30618` event carries `d`, not - `a`, and the kind check only accepts `GitRepoAnnouncement`. A state update arriving - from a relay therefore does not refresh an open repository panel's `head`. - `RepoListStore` is unaffected because its check is kind-based. - -2. **Locally published state events refresh nothing.** - `BackendEvent::Published` is ignored for `RepoState` by both stores - (`crates/signed_state/src/repos.rs` L83-92 and `crates/signed_state/src/repo.rs` - L221-231). The comment in `crates/signed_state/src/backend.rs` L907-909 - ("publishing notifies the repository views") is inaccurate: after an in-app push, - the explore card stays stale until the next unrelated refresh, sync or restart, - even though the event is in the local database. - -3. **Usage counts are similarly under-scoped.** - The Popular ranking (`crates/signed_state/src/repos.rs` L239-261) counts only - issues, PRs and patches present locally, i.e. mostly repositories the user has - opened. - -4. **The Recent tab is not "recently updated".** - `RepoFilter::Recent` (`crates/workspace/src/views/repo_list.rs` L73-84) truncates the - announcement-sorted list (`crates/signed_state/src/repos.rs` L189-190), so it orders - by creation, not by `last_activity`. - -5. **The 90-day activity window is deliberate.** - Activity-driven bumps decay after `ACTIVITY_WINDOW`; only state events would bump - indefinitely once fetched (L13-14). - -## Remediation options - -- **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. — implemented.** - `RepoListStore` fetches each own repository's state event from the relays that - repository announces, once the list discovers it. Driving this from the list rather - than `sync_inbox` sidesteps the login race described above: whenever the announcement - list is (re)loaded, any own repository not yet fetched is covered. The fetched-set is - cleared on `SignerChanged`, so a login or account switch re-fetches. - -- **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. - -A + B + C are implemented.