Refresh checkpoint with contact-aware feed

This commit is contained in:
Avi 2026-08-06 11:03:07 -05:00
commit 387f12fada

View file

@ -1,4 +1,4 @@
# Checkpoint — Feed aggregation (2026-08-06)
# Checkpoint — Contact-aware feed (2026-08-06)
A stopping point you can return to if this session is closed. Everything below was
verified green at the moment this file was written.
@ -6,53 +6,58 @@ verified green at the moment this file was written.
## Where things are
- Project: `/home/avi/Projects/skills/nost-feed-manager`
- Git repo: `master` @ `37a9d94` ("Add feed aggregation from enabled relays"), on top of the
NIP-46 signer work (`8cbe0b8` README, `ca3203c` approval gate, `73cb08d` signer) described in
earlier checkpoints.
- The working tree is **clean** — this session's feed feature and its checkpoint are committed.
- The feed feature spans the Rust `src/feed.rs` module plus IPC/CLI wiring and the frontend
Feed screen.
- Git repo: `master` @ `a1445d1` ("Add contact-aware feed scope").
- Working tree is **clean** — this session's contact-aware feed changes and its checkpoint
are committed.
- On top of that sits the NIP-46 signer work and the original feed aggregation, all committed.
## What was completed: feed aggregation
## What was completed: contact-aware feed
The app now has a **Feed screen** (sidebar nav item with a list icon) that shows a read-only,
newest-first list of recent text notes aggregated from the enabled relays. Reading never touches
the user's stored keys: a throwaway identity is used, and the feed never signs or publishes.
The Feed screen (and CLI) can now be scoped to **My contacts**: instead of showing the global
stream, it fetches the active profile's kind 3 contact list from the enabled relays and returns
only recent notes authored by those contacts. This builds on the existing aggregation engine.
- **Backend** (`src/feed.rs`): `FeedItem` (note id, author hex + npub, content, created_at,
relays seen on), `aggregate_feed(settings, limit)` which connects to every enabled relay,
subscribes to kind 1 notes newer than 24h (capped at `limit`, default 50), collects for up to
15s, then returns de-duplicated, newest-first items. `FeedBuilder` dedupes events across relays
(tracking the extra relay sources) and stops early once the cap is reached. Empty input (no
relays enabled, or no notes) returns an empty list, not an error. Unit tests cover no-relay
empty feed, newest-first sorting, cross-relay dedupe, kind filtering, and the fill cap.
- **IPC** (`src/ipc.rs`): new `Request::FeedGet { limit }` → returns the aggregated items. Needs
no vault unlock, so the feed works even when the vault is locked.
- **CLI** (`src/main.rs`): `feed [limit]` prints a one-line preview per note (content, npub,
timestamp); a friendly "No notes found…" message when the feed is empty.
- **Electron/React frontend**: the **Feed screen** (`FeedScreen.tsx`, wired in `App.tsx` as the
`'feed'` screen). Shows author avatar/npub, content, relative date, a "2 relays" badge for
notes seen on multiple relays, and a **Refresh** button. Empty states for "no relays enabled"
(with a button to jump to the Relays screen) and "no notes found", plus a clear error alert on
failure. Wired through `api.ts` (`feedGet`), `AppProvider`, `navigation.ts` (`'feed'`), and the
sidebar (`Icon 'list'`).
- **Backend** (`src/feed.rs`):
- `contact_feed(settings, limit, owner_hex)` fetches the owner's contact pubkeys
(`contact_pubkeys`) then aggregates only notes from those authors.
- `contact_pubkeys` subscribes to kind 3 (contact list) for the owner, collecting the hex
pubkeys of everyone they follow from `TagStandard::PublicKey` tags, de-duplicated.
- `aggregate_feed` still serves the global feed and now delegates to a private
`aggregate_for(settings, limit, authors)` used by both scopes. When `authors` is set the
NIP-01 filter restricts to those authors **and** `FeedBuilder` drops any note from someone
else (defence in depth for relays that ignore the `authors` filter).
- `FeedBuilder::new(limit, authors)` takes an optional author whitelist.
- `owner_pubkey(npub)` decodes a stored `npub1...` (or hex) to a `PublicKey`; shared by the
IPC server and the CLI.
- **IPC** (`src/ipc.rs`): `FeedGet` now accepts `contacts_only: Option<bool>`. With
`contacts_only: true`, it resolves the active profile's npub, decodes it, and calls
`contact_feed`. Without an active profile it returns a `no_active_profile` error.
- **CLI** (`src/main.rs`): `feed [--contacts] [limit]``--contacts` filters to the active
profile's contacts; the no-results message is adjusted accordingly.
- **Electron/React frontend**: the Feed screen (`FeedScreen.tsx`) gains a segmented **Everyone /
My contacts** scope toggle. "My contacts" is disabled when no profile is active (and silently
forced back to Everyone). Scope-aware empty states ("No notes from your contacts"), and
`feedGet(limit, contactsOnly)` threads the flag from `api.ts` through `AppProvider`.
- **Styles** (`styles.css`): a `.segmented` pill control for the scope toggle.
## Commits this session (newest first)
## Commits
- `37a9d94` "Add feed aggregation from enabled relays" — the full feed feature: `src/feed.rs`,
IPC `FeedGet`, CLI `feed`, the frontend Feed screen + tests, and the README update.
- `a1445d1` "Add contact-aware feed scope" — the full feature: `src/feed.rs`
(`contact_feed`/`contact_pubkeys`/`aggregate_for` + author whitelist), IPC `FeedGet`
`contacts_only`, CLI `feed --contacts`, the frontend scope toggle + styles + tests, README.
(Parent: `1fd057d` "Refresh checkpoint with feed aggregation".)
## How it was verified (all green)
```
cargo test # 81 passed
cargo test # 84 passed
cargo clippy --all-targets # clean
cargo fmt --check # clean
cargo build --release # builds (rebuilt so the GUI runs the new backend)
npm run typecheck # clean (frontend/)
npm run lint # clean (pre-existing module warning only)
npm run format:check # clean
npm test # 75 passed (14 files)
npm test # 78 passed (14 files)
npm run electron:build # compiles the Electron main process
npm run build # rebuilds the React bundle (dist/)
```
@ -60,22 +65,21 @@ npm run build # rebuilds the React bundle (dist/)
## How to resume
1. Open the repo: `cd /home/avi/Projects/skills/nost-feed-manager`
2. State is committed: `git status` should be clean; `git log --oneline -5` shows
`37a9d94` at the top.
3. To try the feed:
- GUI: `cd frontend && npm start`, then the **Feed** screen in the sidebar. It lists recent
notes from the enabled relays; use **Refresh** to re-query. (Note: `npm start` alone does
NOT rebuild the React bundle — run `npm run build` first, or use `npm run dev` +
`NOSTR_GUI_DEV_URL=http://localhost:5173 npm start` for live reload.)
- CLI: `cargo build --release`, then `./target/release/nostr-manager-backend feed 10` prints
up to 10 recent notes from the enabled relays.
2. State is committed: `git status` should be clean; `git log --oneline -5` shows `a1445d1`
at the top.
3. To try the contact feed:
- GUI: open the **Feed** screen and switch **Everyone → My contacts**. You need an active
profile that follows people; the segmented control filters the feed to their notes.
- CLI: `./target/release/nostr-manager-backend feed --contacts 10` prints up to 10 recent
notes from the active profile's contacts.
4. Re-run verification with the commands above.
## Outstanding / next steps (if you continue)
- The feed currently shows the *global* stream from the enabled relays. A natural follow-up is a
**contact-aware feed**: fetch the active profile's kind 3 contact list and filter the feed to
those authors (and optionally show link-card previews per note, as hinted in the original
"feed aggregation" description).
- Live end-to-end check of the feed against public relays (network call) has not been run in this
session; the unit + frontend tests cover the deterministic parts.
- The contact feed depends on the kind 3 contact list being present on the enabled relays; a
profile with no contacts (or a newly created one that has never published a contact list)
produces an empty feed, which is expected but could be surfaced more clearly in the GUI.
- A live end-to-end check of the feed (global and contacts) against public relays has not been
run in this session; the deterministic parts are covered by unit + frontend tests.
- The checkpoint/session discipline in AGENTS.md says to commit the feature first, then the
checkpoint update. This was done: `a1445d1` (feature) then this checkpoint commit.