Refresh checkpoint with NIP-46 remote signer

This commit is contained in:
Avi 2026-08-05 19:28:38 -05:00
commit 593fc6af8b

View file

@ -1,4 +1,4 @@
# Checkpoint — Compose preview + open-source docs (2026-08-04) # Checkpoint — NIP-46 remote signer (2026-08-05)
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,119 +6,82 @@ 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` @ `3eb9fa9` ("Rewrite README for Forgejo hosting"), on top of - Git repo: `master` @ `73cb08d` ("Add NIP-46 remote signer (external signing)"), on top of
`dbc0974` ("Add open-source README and MIT license"), `46d5da7` ("Point checkpoint at current `13efee6` ("Refresh checkpoint with Forgejo README"), `3eb9fa9` ("Rewrite README for Forgejo
HEAD"), `9ec01c9` ("Add AGENTS.md checkpoint workflow rule"), `a052851` (checkpoint refresh), hosting"), and the history described in the previous checkpoint.
`d677125` ("Show preview images and link cards in Compose"), `03f6877` (NIP-98 upload auth),
`3e3467b` (compose preview + attachments), `17e8ace` (Profiles subtitle copy), and
`8eb6685` (reveal-secret-key).
- The working tree is **clean** — everything from this session is committed. - The working tree is **clean** — everything from this session is committed.
- `AGENTS.md` now records the standing rule: every addition must end with an up-to-date checkpoint. - The signer feature is split across one commit (`73cb08d`) that contains both the Rust backend
- `README.md` is an open-source-ready document rewritten for **Forgejo** hosting (no GitHub and the Electron/React frontend.
references; clone/issues/wiki links use the `[YOUR_FORGEJO_INSTANCE_URL]/<OWNER>/<REPO>` Forgejo
format; no git remote is configured in the repo yet, so the instance URL is a labeled
placeholder). The new `LICENSE` file grants MIT (Copyright (c) 2026 Avi). The vault is gitignored,
so no secret material is tracked.
- `/home/avi/Projects/nostr_backend/nostr_backendmanager.md` (old-CLI docs) has been updated
to match reality; it lives outside this repo so it is not part of any commit.
- `/home/avi/Projects/nostr_backend/vlog-website/` (separate, untouched).
## What was completed: compose preview + image attachments ## What was completed: NIP-46 remote signer (bunker role)
The Compose screen now has a **Write / Preview** tab split. The preview renders the note as it The app can now act as a **remote signer** (NIP-46, "bunker") for another Nostr app. Instead of
will appear (profile avatar + npub, the text, and any images), without publishing anything. pasting your nsec into other apps, they send signing requests to this app, which approves them
with the active profile's keys. Decisions made this session: the connected peer is
**auto-approved** once the handshake succeeds, 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.
- **Photos from hyperlinks**: pasting an image URL (`.jpg/.jpeg/.png/.gif/.webp/.avif`) anywhere in - **Backend** (`src/signer.rs`): a hand-rolled NIP-46 implementation (the SDK has no ready-made
the note shows the picture in the preview. On publish, the backend detects the same URLs and tags NIP-46 server). `Signer` exposes `status()`, `connect()`, `disconnect()`, `fail()`,
the event with NIP-92 `imeta` (`url`, `m`) plus the legacy `image` tag, so both new and older `set_connected()`. `parse_connect_uri` accepts `nostrconnect://` links (and clearly rejects the
Nostr clients render the images. Detection is by extension only — nothing is downloaded to decide. opposite-role `bunker://` scheme). The async `run_sign_task` connects over the chosen relays,
- **Attach images**: an "Attach image" button opens the native file dialog (multi-select), listens for kind 24133 requests decrypted with a NIP-44 v2 conversation key, and answers
uploads each file to nostr.build (anonymous `POST /api/v2/upload/files`, field `fileToUpload`), `get_public_key`, `sign_event`, and `nip44_encrypt`/`nip44_decrypt`. Responses are NIP-44-encrypted
and shows a removable thumbnail chip. The hosted URLs are appended to the note on publish. and published back to the peer. Six unit tests cover URI parsing, bunker rejection, NIP-44
- **Where upload lives**: `pick_image` / `upload_image` are handled by the Electron main process round-trip, get_public_key, sign_event, and unknown-method error responses.
(native dialog + `fetch`), not the Rust backend — they need a file dialog and a one-off HTTP - **IPC** (`src/ipc.rs`): `serve`/`handle`/`run` now share `Arc<Mutex<App>>`; new variants
upload. The renderer still calls them through the same `window.backend.request` envelope. `SignerConnect { uri }`, `SignerDisconnect`, `SignerStatus`. The connect/disconnect/status
- The reveal-secret-key feature from the previous commit (`8eb6685`) is unchanged and still works. 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>`.
- **Electron/React frontend**: new **Signer screen** (sidebar nav item with the key icon) where you
paste a `nostrconnect://` link, Connect/Disconnect, and see the phase (Not connected /
Connecting… / Connected), the connected client's hex pubkey (shortened), the relays in use, and
any error. Wired through `api.ts` (`signerConnect`/`signerDisconnect`/`signerStatus`) and
`AppProvider`. When the vault is locked, a warning explains the signer needs an unlocked vault.
Tests: `SignerScreen.test.tsx` (new) + `apiMock.ts`/`fakeBackend.ts` signer methods.
## Commits this session (newest first) ## Commits this session (newest first)
- `3eb9fa9` "Rewrite README for Forgejo hosting" — README now targets Forgejo: zero GitHub URLs; - `73cb08d` "Add NIP-46 remote signer (external signing)" — everything above in one commit.
clone / issues / wiki links use the generic Forgejo format `[YOUR_FORGEJO_INSTANCE_URL]/<OWNER>/<REPO>` Full verification suite re-run green (below).
with the placeholder labeled as such (repo has no git remote configured, so the instance URL
couldn't be auto-detected). Forgejo Actions CI badge left as a commented placeholder. Full
verification suite re-run green (docs-only change): cargo 64, npm 65, clippy/fmt/typecheck/
lint/format/builds clean.
- `dbc0974` "Add open-source README and MIT license" — `README.md` rewritten for a technical /
open-source audience (badges, feature list incl. compose preview + media, tech-stack tables,
quick start + `.env` example, CLI/IPC usage, screenshots placeholders, dev guide, contributing,
community, license). New `LICENSE` (MIT, Copyright (c) 2026 Avi). Docs-only change; full
verification suite re-run green anyway (cargo 64, npm 65, all lint/format/builds clean).
- `46d5da7` "Point checkpoint at current HEAD" — checkpoint aligned with HEAD before the docs work.
- `9ec01c9` "Add AGENTS.md checkpoint workflow rule" — new `AGENTS.md` records that every addition
must end with an up-to-date checkpoint.
- `a052851` "Refresh checkpoint with preview image/link fixes"
- `d677125` "Show preview images and link cards in Compose" — two fixes from GUI testing:
1. Remote images never rendered because the CSP was `img-src 'self' data:` — added `https:`
(`frontend/index.html`). Attached images and pasted image URLs now appear in the preview.
2. Link previews: a pasted web-page link (not a direct image URL) now fetches the page in the
Electron main process (`link_preview` IPC) and shows an OpenGraph/Twitter card (image, title,
description, site, hostname) in the preview. New `src/lib/media.ts::extractLinkUrls`,
`LinkPreview` type, `api.linkPreview`, `AppProvider.linkPreview`, `LinkPreviewCard` in
ComposeScreen, `styles.css` card styles. Publish is unchanged — link cards are a preview-only
feature (Nostr clients render their own link previews from the URL in the note).
Tests: `media.test.ts` (new), `ComposeScreen.test.tsx`, `apiMock.ts`, `fakeBackend.ts`.
- `03f6877` "Sign NIP-98 auth for nostr.build image uploads" — fixes the attach-image flow, which
failed with "Unauthorized, please provide a valid nip-98 token". nostr.build now requires a
NIP-98 `Authorization` header. Backend: new `src/uploads.rs` (`nip98_authorization` signs a kind
27235 auth event with the active profile's key and returns `Nostr <base64>`), IPC method
`upload_auth { url, http_method }`, `nostr-sdk` feature `nip98` added. Electron:
`frontend/electron/main.ts` fetches the header from the backend and sends it on the upload.
Verified live: throwaway profile → IPC `upload_auth``curl` upload to nostr.build returned
`status: success` (temp data cleaned up).
- `3e3467b` "Add compose preview with image attachments (NIP-92 imeta)" — 11 files, 1 new.
Backend: `src/publish.rs` (`extract_image_urls`, `image_mime_from_url`, `image_tags` + tests).
Electron: `frontend/electron/main.ts` (`pickImage`, `uploadImage`, routed from `backend:request`).
Renderer: `frontend/src/lib/media.ts` (new), `api.ts`, `types.ts`, `AppProvider.tsx`,
`ComposeScreen.tsx`, `styles.css`. Tests: `ComposeScreen.test.tsx`, `apiMock.ts`, `fakeBackend.ts`
(adds request recording + `pick_image`/`upload_image`).
- `17e8ace` "Clarify the Profiles subtitle about private keys" — `ProfilesScreen.tsx` copy.
- `8eb6685` "Add reveal-secret-key after unlock (CLI + GUI)" — previous feature, see history.
## How it was verified (all green) ## How it was verified (all green)
``` ```
cargo test # 64 passed cargo test # 70 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 # 65 passed (12 files) npm test # 69 passed (13 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/)
``` ```
Plus live checks: a throwaway-profile `upload_auth` + `curl` upload to nostr.build returned CLI smoke checks run earlier in the session: `signer status` printed the active profile + relays;
`status: success`; the link-preview parser (same code as Electron) fetched Wikipedia's og:title + a `bunker://` link and a non-`nostrconnect` URI were both rejected with clear errors.
og:image and example.com's title correctly. Temp files cleaned up.
## 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 the three commits. 2. State is committed: `git status` should be clean; `git log --oneline -5` shows
3. To try it: `73cb08d` at the top.
- GUI: `cd frontend && npm start` — Compose → "Attach image" or paste an image URL → Preview tab. 3. To try the signer:
(Note: `npm start` alone does NOT rebuild the React bundle — run `npm run build` first, or use - GUI: `cd frontend && npm start`, then the **Signer** screen in the sidebar. Paste a
`npm run dev` + `NOSTR_GUI_DEV_URL=http://localhost:5173 npm start` for live reload.) `nostrconnect://` link from a Nostr app to sign for it. (Note: `npm start` alone does NOT
- CLI: `cargo build --release`, then `./target/release/nostr-manager-backend list` and rebuild the React bundle — run `npm run build` first, or use `npm run dev` +
`./target/release/nostr-manager-backend show-secret <npub>` (no angle brackets). `NOSTR_GUI_DEV_URL=http://localhost:5173 npm start` for live reload.)
- CLI: `cargo build --release`, then `./target/release/nostr-manager-backend signer status`.
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)
- Nothing uncommitted. Possible follow-ups: a real feed view (currently Home only shows the last - Nothing uncommitted. Possible follow-ups: a real feed view (currently Home only shows the last
publication); the upload uses the active profile's key for NIP-98 (so an encrypted, locked vault publication); a NIP-46 **client** role (initiating `bunker://` from this app is intentionally
needs unlocking before attaching an image — the attach error explains this); relay defaults in code not implemented — only the signer role is). Live cross-client verification against an actual
still point at `relay.damus.io` (503 upstream) and `relay.nostr.band` (timeout) — user's local Nostr app (e.g. a NIP-46-compatible client that produces a `nostrconnect://` link) has not yet
settings already use `nos.lol` + `relay.primal.net` instead. been done end-to-end, only the unit and CLI smoke tests.