checkpoint: document packaging icon fix (post-Step 2)

HEAD moved to 114b343 (icon restored); the previously-deleted
frontend/build/icon.png is now a committed asset, no longer a leftover.
Records the verified npm run dist + AppImage/deb icon proof, the
pre-existing format:check failure (5 files, Step-7), and notes the
homepage rename is on the record for Step 7 (not fixed). Step 3 signer
API is proposed and awaiting sign-off, not implemented.
This commit is contained in:
Avi 2026-09-03 13:14:44 -05:00
commit ee88171e45

View file

@ -1,20 +1,22 @@
# Checkpoint — Signer Modes + Fail-Closed Key Export (2026-09-03)
# Checkpoint — Signer Modes + Fail-Closed Key Export + Packaging Icon (2026-09-03)
## Where things are
- Project: `/home/avi/Projects/Keynctr`
- Branch: `master` @ **`6eff510`** ("feat: fail-closed ExportSecretKey with fresh auth and audit").
- Working tree: **not clean** — see "Still uncommitted" below. The three feature
commits of this session are committed; only the packaging icon, the old
checkpoint, and pre-existing hygiene leftovers remain uncommitted.
- Branch: `master` @ **`114b343`** ("fix(packaging): restore Linux app icon so dist builds ship an icon").
- Working tree: **effectively clean for tracked files.** No tracked file is modified or
staged. The only untracked entries are the pre-existing hygiene leftovers and the
source JPEG (all intentionally untracked — see "Still untracked"). Build artifacts
(`release/`, `dist/`) are gitignored.
## What was completed (this session)
The previously-uncommitted working tree (27 modified + 10 untracked files, ~1852/692)
was triaged into **three logical, independently-verifiable commits**. Order is
`a → c → b`: `ExportSecretKey` (b) reads the per-profile `signer_mode` field and the
`external_signer_not_connected` error that the signer-mode commit (c) introduces, so
(c) had to land first. The only uncommitted *tests* were the export tests and the
signer-mode/permission tests, so "(d) tests" is not a separate commit — each feature's
tests travel with it.
was triaged into **three logical, independently-verifiable commits** in a prior session,
and this session **landed the Step-2 packaging fix** on top. Order is
`a → c → b → (packaging)`: `ExportSecretKey` (b) reads the per-profile `signer_mode`
field and the `external_signer_not_connected` error that the signer-mode commit (c)
introduces, so (c) had to land first. The only uncommitted *tests* were the export
tests and the signer-mode/permission tests, so "(d) tests" is not a separate commit —
each feature's tests travel with it.
1. **Per-profile signer modes + persisted NIP-46 connections (c)** — three coexisting
signing modes (Embedded / Nip46Client / Nip46Bunker), a `signer_mode` field on every
@ -28,6 +30,17 @@ tests travel with it.
replaces the old reveal modal.
3. **Hash-chained audit log (a)** — SHA-256 hash-chained append-only log with atomic
append and end-to-end `verify_chain()`; the `sha2` dependency.
4. **Packaging icon restored (this session, `114b343`)** — `frontend/build/icon.png`
was deleted from the working tree, so electron-builder had no Linux icon and every
AppImage/deb it emitted carried the generic Electron placeholder. The icon was
**regenerated from `KeynectrAppIconPossibility02.jpeg`** (not resized): the white
(254) JPEG background was cut to transparent (alpha derived from luma), the art was
flattened to a square canvas with symmetric padding, ink kept pure black (RGB 0,0,0),
and exported as **512x512 RGBA**. The single declared config path
(`linux.icon: build/icon.png`) was kept single — no `build/linux/` fan-out — because
the 512 master satisfies both targets: AppImage downscales to 256 internally (≥256
required) and the deb installs `usr/share/icons/hicolor/512x512/apps/keynectr.png`,
matching the generated `.desktop` `Icon=keynectr`.
### Security properties confirmed
- No secret material is logged or returned except the single, authenticated, audited
@ -45,67 +58,81 @@ tests travel with it.
## Commits added this session (newest first)
| Hash | Message |
|------|---------|
| `6eff510` | feat: fail-closed ExportSecretKey with fresh auth and audit |
| `2c61830` | feat: add per-profile signer modes with persisted NIP-46 connections |
| `caed722` | feat: add hash-chained, append-only audit log |
| `114b343` | fix(packaging): restore Linux app icon so dist builds ship an icon |
Parent of this session: `4038e2d` ("checkpoint: document embedded signer and NIP-46
client signer modes").
(The prior session's three feature commits and their checkpoint `d92371f` remain the
parent chain: `6eff510` → `2c61830` → `caed722` → … → `d92371f` → `114b343`.)
### Files touched by the three commits (30 files, +3921 / -611)
```
Cargo.lock Cargo.toml frontend/electron/main.ts frontend/package.json
frontend/package-lock.json frontend/src/components/ExportSecretKeyModal.tsx (new)
frontend/src/components/ShowSecretKeyModal.tsx (deleted)
frontend/src/lib/api.ts frontend/src/lib/signer/SignerManager.ts (new)
frontend/src/lib/types.ts frontend/src/screens/ProfilesScreen.tsx
frontend/src/screens/SignerModeScreen.tsx frontend/src/state/AppProvider.tsx
frontend/src/styles.css frontend/src/test/apiMock.ts
frontend/src/test/ExportSecretKey.test.tsx (new) frontend/src/test/fakeBackend.ts
frontend/src/test/ShowSecretKey.test.tsx (deleted)
src/app.rs src/audit.rs (new) src/errors.rs src/ipc.rs src/lib.rs src/main.rs
src/profiles.rs src/signer/mod.rs src/signer/nip46_client.rs
src/signer/permissions.rs (new) src/signer/types.rs src/vault.rs
```
## Verification (run this session, per commit)
Each commit was verified **in isolation** (checked out on top of its parent), not just
as the final tree:
- `caed722` (audit): `cargo test --release` → **124 passed** (119 prior + 5 audit).
- `2c61830` (signer modes): `cargo test --release` → **186 passed**; `cargo clippy
--all-targets` clean; `cargo fmt --check` clean; frontend `tsc --noEmit` clean;
`npx vitest run` → **110 passed**.
- `6eff510` (export): `cargo test --release` → **186 passed**; frontend `tsc --noEmit`
clean; `npx vitest run` → **116 passed** (110 + 6 export tests).
## Verification (run this session)
Full suite per AGENTS.md, run on top of `114b343` (icon is a binary asset, no code
change, so the suite is expected to hold):
- **Rust**: `cargo test --release` → **186 passed**, 0 failed; `cargo clippy
--all-targets` → clean (exit 0); `cargo fmt --check` → clean (exit 0); `cargo build
--release` → Finished, exit 0.
- **Frontend**: `npm test` → **116 passed** (16 files); `npm run typecheck` → clean
(exit 0); `npm run lint` → clean (exit 0).
- `npm run format:check` → **exit 1** on 5 files (`src/components/ExportSecretKeyModal.tsx`,
`src/screens/SignerModeScreen.tsx`, `src/state/AppProvider.tsx`,
`src/test/ExportSecretKey.test.tsx`, `src/test/fakeBackend.ts`). **Pre-existing** —
those files are committed as-is at `6eff510` (prior session) and are clean in the
working tree; this icon-only change touched none of them. Left for the Step-7
hygiene/format pass; not silently auto-fixed here.
- **Packaging (the point of this fix)**: `npm run dist` ran end-to-end. Note the script
is `electron-builder --linux dir`, which builds only the unpacked dir; the declared
`linux.target` (AppImage + deb) was also built directly to prove the icon lands:
- `release/Keynctr-0.1.0.AppImage` — 135,749,547 bytes.
- `release/keynectr_0.1.0_amd64.deb` — 105,810,972 bytes.
- **Icon proven inside both artifacts**: the embedded icon is **byte-identical
(md5 `b3e372f7`)** to the generated `build/icon.png` in all four locations checked —
the deb's `usr/share/icons/hicolor/512x512/apps/keynectr.png`, the AppImage's hicolor
png, the AppImage's `.DirIcon`, and `build/icon.png` itself — all 512x512 RGBA with
real transparency (32,626 opaque px, 226,582 transparent, corners alpha 0), ink
pure black. The deb `.desktop` reads `Icon=keynectr`, matching the hicolor name.
## How to reproduce / exercise
- Backend: `cargo run --release -- serve` (JSON-lines IPC on stdio) or the CLI in
`src/main.rs`.
- GUI: from `frontend/`, `npm run electron:build && electron .` (prod) or `npm run
start:dev` with `NOSTR_GUI_DEV_URL`.
- Packaging: from `frontend/`, `npm run dist` (unpacked dir) or `npx electron-builder
--linux AppImage deb` (declared targets) → artifacts in `release/`.
- Exercise export: Profiles → a profile → Export secret key → enter the vault password
and a reason. An external (NIP-46 client) profile shows it cannot export.
- Exercise modes: Signer Mode screen → pick Embedded / NIP-46 client; connect a
`nostrconnect://` URI with a `perms=` parameter to grant scoped permissions.
## Still uncommitted (do NOT lose; do NOT commit the hygiene junk)
- **`frontend/build/icon.png` is deleted** (working tree) — the electron-builder Linux
icon. This is the Step-2 packaging fix: regenerate it from
`KeynectrAppIconPossibility02.jpeg` (or point electron-builder at the new asset),
verify `npm run dist`, then commit. **Not done in this session.**
- **`CHECKPOINT-encryption.md`** — this file, committed to reference the post-triage
HEAD (`6eff510`). It must be re-updated to reference the new HEAD once Step 2
(packaging) lands its own commit.
- Pre-existing untracked hygiene leftovers (out of scope, address in the hygiene pass):
`COSMIC_THEME.md`, `KeynectrAppIconPossibility02.jpeg`, `.opencode/`, `.impeccable/`,
`.directory`. **`profiles_vault.json*` and `target/` remain correctly untracked and
uncommitted** (vault is gitignored).
## Still untracked (do NOT lose; do NOT commit the hygiene junk)
- **Source JPEG** `KeynectrAppIconPossibility02.jpeg` — intentionally **untracked**
(the icon is now derived from it; the JPEG itself is not a build input and stays out
of git, per instruction).
- Pre-existing untracked hygiene leftovers (out of scope, address in the Step-7 hygiene
pass): `COSMIC_THEME.md`, `.opencode/`, `.impeccable/`, `.directory`.
- **`frontend/build/icon.png` is no longer a leftover** — it was regenerated and
committed in `114b343` this session. The prior "deleted icon" item is closed.
- Build artifacts `release/` and `dist/` are gitignored and not committed.
- `profiles_vault.json*` and `target/` remain correctly untracked and uncommitted
(vault is gitignored).
## On the record (not fixed, per instruction)
- **`package.json` → `homepage` still reads `https://github.com/avi/Keynctr`.** This is
the Step-7 rename/hygiene item and was **left untouched** in this session; noted here
so it is on the record rather than silently fixed.
## Deferred / next steps (unchanged, plus new)
- **Step 2 (packaging)**: restore `frontend/build/icon.png`, verify `npm run dist`.
- Signer abstraction (Step 3): promote `src/signer` to the single `Signer` source of
truth; introduce `SigningBackend { Internal{..}, Remote{..} }` per profile and route
every IPC handler through the trait (no inline mode branching).
- **Step 2 (packaging): DONE.** Icon restored, `npm run dist` + declared targets green,
icon proven inside both artifacts, committed as `114b343`.
- **Step 3 (signer abstraction)** — proposed for sign-off, NOT yet implemented. Current
state that motivates the API: `src/signer/mod.rs` already defines a `Signer` trait and
a `Signing` enum (`Local(Keys)` / `External{signer, profile_pubkey}`); `SignerMode`
(`Embedded`/`Nip46Bunker`/`Nip46Client`) lives on `StoredProfile` (per-profile) *and*
is duplicated on `App` (app-level), and `App` holds three separate signer handles
(`embedded_signer`, `nip46_signer`, `nip46_bunker_signer`). The IPC dispatcher
(`src/ipc.rs`) branches on `signer_mode`/handle presence in ~12 sites (see the
grep list pasted to the user this session). The proposal promotes `src/signer` to the
single `Signer` source of truth, introduces a `SigningBackend { Internal{..},
Remote{..} }` per profile, and routes every IPC handler through the trait so no inline
mode branching remains. **Awaiting the user's sign-off on the API before any
implementation.**
- External-signer permissions (Step 4): wire `src/signer/permissions.rs` into the
approval modal so grants (kinds, relays, expiry, rate) are persisted AND enforced in
the UI, not just parsed.
@ -113,8 +140,10 @@ as the final tree:
+ backward-compatible migration; `--allow-env-secret` flag; remove or gate deprecated
`RevealSecretKey` IPC behind the same fail-closed path.
- Undo history (Step 6): resolve the `ProfileSummary`-loses-the-secret question.
- Hygiene (Step 7): Keynctr rename pass, delete legacy Python, migrate root
`profiles_vault.json*` into `~/.local/share/keynectr`.
- Hygiene (Step 7): Keynctr rename pass (includes the `homepage` → correct repo fix noted
above), delete legacy Python, migrate root `profiles_vault.json*` into
`~/.local/share/keynectr`, and a Prettier format pass to clear the 5 pre-existing
`format:check` failures.
- Open question carried forward: `migrate_vault_signer_modes` currently reports a change
on every load (always `changed = true`), so `App::load` re-saves the vault each start.
Harmless (idempotent) but wasteful; tighten to only report real changes.