diff --git a/CHECKPOINT-encryption.md b/CHECKPOINT-encryption.md index fabed80..ba408ba 100644 --- a/CHECKPOINT-encryption.md +++ b/CHECKPOINT-encryption.md @@ -1,4 +1,4 @@ -# Checkpoint — NIP-46 remote signer + approval gate (2026-08-06) +# Checkpoint — Feed aggregation (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,102 +6,76 @@ verified green at the moment this file was written. ## Where things are - Project: `/home/avi/Projects/skills/nost-feed-manager` -- Git repo: `master` @ `ca3203c` ("Require user approval for NIP-46 sign/encrypt requests"), - on top of `593fc6a` ("Refresh checkpoint with NIP-46 remote signer"), `73cb08d` ("Add NIP-46 - remote signer (external signing)"), `13efee6` ("Refresh checkpoint with Forgejo README"), - `3eb9fa9` ("Rewrite README for Forgejo hosting"), and the history described in earlier - checkpoints. -- The working tree is **clean** — everything from this session is committed. -- The signer feature spans `73cb08d` (the signer itself) plus `ca3203c` (the approval gate). +- 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. -## What was completed: NIP-46 remote signer (bunker role) +## What was completed: feed aggregation -The app can now act as a **remote signer** (NIP-46, "bunker") for another Nostr app. Instead of -pasting your nsec into other apps, they send signing requests to this app, which approves them -with the active profile's keys. The signer runs **inside the `serve` process as a concurrent -tokio task** (sharing `Arc>`), and the frontend gets a **dedicated Signer screen** as -its own sidebar nav item. +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. -**Security change this session (commit `ca3203c`): every key-using request now requires explicit -user approval.** The peer is still "trusted" after the handshake, but `sign_event`, -`nip44_encrypt`, and `nip44_decrypt` are no longer auto-approved. Each such request is parked in a -`pending` queue, exposed in `SignerStatus.pending`, and only runs after the user clicks -**Approve** on the Signer screen (or is answered with **Reject** / a 300-second timeout). All -non-key methods (`connect`, `get_public_key`, `ping`, `get_relays`, `switch_relays`, `logout`) -still run immediately. - - - **Backend** (`src/signer.rs`): a hand-rolled NIP-46 implementation (the SDK has no ready-made - NIP-46 server). `Signer` exposes `status()`, `connect()`, `disconnect()`, `fail()`, - `set_connected()`, `approve()`, and `await_approval()`. `parse_connect_uri` accepts - `nostrconnect://` links (and clearly rejects the opposite-role `bunker://` scheme). The async - `run_sign_task` connects over the chosen relays, listens for kind 24133 requests decrypted with - a NIP-44 v2 conversation key, and answers `get_public_key`, `sign_event`, and - `nip44_encrypt`/`nip44_decrypt`. Responses are NIP-44-encrypted and published back to the peer. - Key-using methods route through `gated_response` → `await_approval` (a `HashMap` of pending - entries, each with a `oneshot` decision channel) → `approved_response`, while everything else - flows through the non-blocking `handle_request` dispatcher. The pending list clears on - disconnect and on failure, so a slow/abandoned approval never leaks into a new connection. - Unit tests cover URI parsing, bunker rejection, NIP-44 round-trip, get_public_key, sign_event, - unknown-method, and the approval gate (wait-then-sign, reject-never-uses-key, unknown-id error). -- **IPC** (`src/ipc.rs`): `serve`/`handle`/`run` share `Arc>`; variants - `SignerConnect { uri }`, `SignerDisconnect`, `SignerStatus`, and the new `SignerApprove { id, - approved }`. The signer-status/approve requests are serviced by `run()` before the (long-lived) - vault lock so a slow signer task never blocks ordinary commands. -- **CLI** (`src/main.rs`): `signer status` and `signer connect `; `signer status` notes that - approvals happen in the GUI (the CLI's blocking `connect` loop cannot answer approvals itself). -- **Electron/React frontend**: the **Signer screen** (sidebar nav item with the key icon) where you - paste a `nostrconnect://` link, **Connect**/**Disconnect**, see the phase, peer, relays, and any - error, and now review pending requests with **Approve**/**Reject** buttons. The screen polls - `signer_status` every second while mounted so approval requests appear without a manual - refresh. Wired through `api.ts` (`signerConnect`/`signerDisconnect`/`signerStatus`/ - `signerApprove`) and `AppProvider`. + - **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'`). ## Commits this session (newest first) -- `ca3203c` "Require user approval for NIP-46 sign/encrypt requests" — the approval gate - (backend pending queue + `SignerApprove` IPC + Signer screen Approve/Reject + tests). -- `593fc6a` "Refresh checkpoint with NIP-46 remote signer" — checkpoint refresh (previous session). -- `73cb08d` "Add NIP-46 remote signer (external signing)" — the signer feature itself. +- `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. ## How it was verified (all green) ``` -cargo test # 75 passed +cargo test # 81 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 # 71 passed (13 files) +npm test # 75 passed (14 files) npm run electron:build # compiles the Electron main process npm run build # rebuilds the React bundle (dist/) ``` -CLI smoke checks run earlier in the session: `signer status` printed the active profile + relays; -a `bunker://` link and a non-`nostrconnect` URI were both rejected with clear errors. - ## 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 - `ca3203c` at the top. -3. To try the signer: - - GUI: `cd frontend && npm start`, then the **Signer** screen in the sidebar. Paste a - `nostrconnect://` link from a Nostr app to sign for it. When the app sends a sign/decrypt - request it appears under "Requests waiting for approval" — click **Approve** or **Reject**. - (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 signer status`. - Note: the CLI's blocking `signer connect` loop cannot answer approval prompts — run the GUI - for that. + `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. 4. Re-run verification with the commands above. ## Outstanding / next steps (if you continue) -- Nothing uncommitted. Possible follow-ups: a real feed view (currently Home only shows the last - publication); a NIP-46 **client** role (initiating `bunker://` from this app is intentionally - not implemented — only the signer role is); a CLI-side approval escape hatch for `signer - connect` (prompt on stdin) so approvals also work headlessly. Live cross-client verification - against an actual Nostr app (e.g. a NIP-46-compatible client that produces a `nostrconnect://` - link) has not yet been done end-to-end, only the unit and CLI smoke tests. \ No newline at end of file +- 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