diff --git a/CHECKPOINT-encryption.md b/CHECKPOINT-encryption.md index ba408ba..f6466cc 100644 --- a/CHECKPOINT-encryption.md +++ b/CHECKPOINT-encryption.md @@ -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`. 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. \ No newline at end of file +- 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. \ No newline at end of file