Refresh checkpoint with feed aggregation
This commit is contained in:
parent
37a9d945c9
commit
1fd057d3e1
1 changed files with 49 additions and 75 deletions
|
|
@ -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<Mutex<App>>`), 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<Mutex<App>>`; 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 <uri>`; `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.
|
||||
- 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue