This commit is contained in:
2026-09-25 17:34:39 +07:00
parent 0ff6740a9e
commit 1d44338037
2 changed files with 147 additions and 0 deletions
+1
View File
@@ -7,6 +7,7 @@
### Added ### Added
- Show an avatar in each panel's tab, using the repository owner's profile picture when set and a pixel avatar otherwise - Show an avatar in each panel's tab, using the repository owner's profile picture when set and a pixel avatar otherwise
- Add a Tab Bar setting to hide the previous/next tab buttons, hidden by default
### Changed ### Changed
+146
View File
@@ -0,0 +1,146 @@
# 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).**
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.
- **B. Targeted fetch for the user's own repositories.**
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.
Suggested combination: A + C, plus B if own-repository freshness on fresh profiles
matters.