Refresh session checkpoint for compose media feature

This commit is contained in:
Avi 2026-08-04 13:30:54 -05:00
commit 8b701db929

View file

@ -1,4 +1,4 @@
# Checkpoint — Secret-key reveal after unlock (2026-08-04)
# Checkpoint — Compose preview with image attachments (2026-08-04)
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,87 +6,72 @@ verified green at the moment this file was written.
## Where things are
- Project: `/home/avi/Projects/skills/nost-feed-manager`
- Git repo: `master`
- The reveal feature is **uncommitted** — all changes are in the working tree.
- Git repo: `master` @ `3e3467b` ("Add compose preview with image attachments (NIP-92 imeta)"),
on top of `17e8ace` ("Clarify the Profiles subtitle about private keys") and `8eb6685`
("Add reveal-secret-key after unlock (CLI + GUI)").
- The working tree is **clean** — everything from this session is committed.
- `/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 the commit.
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 in this session: reveal a secret key after unlock
## What was completed: compose preview + image attachments
Building on the existing password-encrypted vault (AES-256-GCM + Argon2id), owners can now view a
profile's secret key after entering the vault password:
The Compose screen now has a **Write / Preview** tab split. The preview renders the note as it
will appear (profile avatar + npub, the text, and any images), without publishing anything.
- Backend: `profiles::reveal_secret_key` returns the key in both hex and `nsec1...` forms, gated on
an unlocked vault (`VaultLocked` when encrypted + locked). New CLI command `show-secret <npub>`,
which prompts for the password when locked (via `NFM_PASSWORD` env or hidden prompt).
- IPC: new `reveal_secret_key { npub }` method. Error replies now carry a machine-readable `code`
field (ErrorKind serialised as snake_case, e.g. `vault_locked`), so the GUI can branch without
string-matching on user-facing messages.
- GUI: a "Secret key" button on every profile card opens `ShowSecretKeyModal`, which shows hex +
nsec with copy buttons and a warning. When the vault is locked the modal asks for the password
inline, unlocks, then reveals.
- Key is only ever fetched after unlock; never stored in state before reveal.
- **Photos from hyperlinks**: pasting an image URL (`.jpg/.jpeg/.png/.gif/.webp/.avif`) anywhere in
the note shows the picture in the preview. On publish, the backend detects the same URLs and tags
the event with NIP-92 `imeta` (`url`, `m`) plus the legacy `image` tag, so both new and older
Nostr clients render the images. Detection is by extension only — nothing is downloaded to decide.
- **Attach images**: an "Attach image" button opens the native file dialog (multi-select),
uploads each file to nostr.build (anonymous `POST /api/v2/upload/files`, field `fileToUpload`),
and shows a removable thumbnail chip. The hosted URLs are appended to the note on publish.
- **Where upload lives**: `pick_image` / `upload_image` are handled by the Electron main process
(native dialog + `fetch`), not the Rust backend — they need a file dialog and a one-off HTTP
upload. The renderer still calls them through the same `window.backend.request` envelope.
- The reveal-secret-key feature from the previous commit (`8eb6685`) is unchanged and still works.
## Files changed (16 modified, 2 new)
## Commits this session (newest first)
Modified:
- `README.md` — documented the reveal feature + `show-secret`
- `src/errors.rs``ErrorKind` now serialises as snake_case for the IPC error code
- `src/profiles.rs``RevealedKey`, `reveal_secret_key`, `profile_label` + tests
- `src/ipc.rs``RevealSecretKey` request, `code` on error replies
- `src/main.rs``show-secret` CLI command
- `frontend/src/components/Icon.tsx` — new `key` icon
- `frontend/src/lib/api.ts``revealSecretKey`, `BackendError.code`
- `frontend/src/lib/types.ts``RevealedKey`, error `code` in `BackendResponse`
- `frontend/src/screens/ProfilesScreen.tsx` — "Secret key" button per profile
- `frontend/src/state/AppProvider.tsx``revealSecretKey` in context
- `frontend/src/test/{App,ProfilesScreen,apiMock,fakeBackend}` — updated for new UI + error codes
New:
- `frontend/src/components/ShowSecretKeyModal.tsx`
- `frontend/src/test/ShowSecretKey.test.tsx`
Also updated (outside repo): `/home/avi/Projects/nostr_backend/nostr_backendmanager.md`.
## New backend API (IPC + CLI)
IPC: `reveal_secret_key { npub }``{ hex, nsec }`. Error replies now include
`"code": "vault_locked"` (etc.) alongside `message`/`details`.
CLI: `show-secret <npub>` prints hex + nsec after unlocking. `create`, `publish`, and
`show-secret` all auto-prompt for the vault password when it is encrypted.
- `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)
```
cargo test # 55 passed
cargo test # 60 passed
cargo clippy --all-targets # clean
cargo fmt --check # clean
cargo build --release # builds
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 # 56 passed (11 files)
npm test # 60 passed (11 files)
npm run build # rebuilds the React bundle (dist/)
npm run electron:build # compiles the Electron main process
```
Plus a manual IPC end-to-end smoke test: reveal on plaintext → OK; set-password → lock →
reveal returns `code: "vault_locked"`; unlock → reveal returns the same hex. Temp data cleaned up.
## How to resume
1. Open the repo: `cd /home/avi/Projects/skills/nost-feed-manager`
2. Inspect the diff: `git diff` (work is still uncommitted)
2. State is committed: `git status` should be clean; `git log --oneline -5` shows the three commits.
3. To try it:
- CLI: `cargo build --release`, then
`XDG_DATA_HOME=/tmp/nfm-smoke ./target/release/nostr-manager-backend create "Alice"` and
`./target/release/nostr-manager-backend show-secret <npub>`
- GUI: `cd frontend && npm start`, Profiles → "Secret key" on a card
- GUI: `cd frontend && npm start` — Compose → "Attach image" or paste an image URL → Preview tab.
(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 list` and
`./target/release/nostr-manager-backend show-secret <npub>` (no angle brackets).
4. Re-run verification with the commands above.
## Outstanding / next steps (if you continue)
- Decide whether to **commit** the reveal work (nothing is committed yet).
- Consider a small UI hint that profiles with an unencrypted vault can be revealed with no prompt.
- Relay defaults are currently `relay.damus.io` (503 upstream) and `relay.nostr.band` (timeout);
user's local settings already point at `nos.lol` + `relay.primal.net` instead.
- Nothing uncommitted. Possible follow-ups: a real feed view (currently Home only shows the last
publication); optional NIP-98 auth for nostr.build uploads; relay defaults in code still point at
`relay.damus.io` (503 upstream) and `relay.nostr.band` (timeout) — user's local settings already
use `nos.lol` + `relay.primal.net` instead.