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