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 ## Where things are
- Project: `/home/avi/Projects/Keynctr` - Project: `/home/avi/Projects/Keynctr`
- Branch: `master` @ **`6eff510`** ("feat: fail-closed ExportSecretKey with fresh auth and audit"). - Branch: `master` @ **`114b343`** ("fix(packaging): restore Linux app icon so dist builds ship an icon").
- Working tree: **not clean** — see "Still uncommitted" below. The three feature - Working tree: **effectively clean for tracked files.** No tracked file is modified or
commits of this session are committed; only the packaging icon, the old staged. The only untracked entries are the pre-existing hygiene leftovers and the
checkpoint, and pre-existing hygiene leftovers remain uncommitted. source JPEG (all intentionally untracked — see "Still untracked"). Build artifacts
(`release/`, `dist/`) are gitignored.
## What was completed (this session) ## What was completed (this session)
The previously-uncommitted working tree (27 modified + 10 untracked files, ~1852/692) The previously-uncommitted working tree (27 modified + 10 untracked files, ~1852/692)
was triaged into **three logical, independently-verifiable commits**. Order is was triaged into **three logical, independently-verifiable commits** in a prior session,
`a → c → b`: `ExportSecretKey` (b) reads the per-profile `signer_mode` field and the and this session **landed the Step-2 packaging fix** on top. Order is
`external_signer_not_connected` error that the signer-mode commit (c) introduces, so `a → c → b → (packaging)`: `ExportSecretKey` (b) reads the per-profile `signer_mode`
(c) had to land first. The only uncommitted *tests* were the export tests and the field and the `external_signer_not_connected` error that the signer-mode commit (c)
signer-mode/permission tests, so "(d) tests" is not a separate commit — each feature's introduces, so (c) had to land first. The only uncommitted *tests* were the export
tests travel with it. 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 1. **Per-profile signer modes + persisted NIP-46 connections (c)** — three coexisting
signing modes (Embedded / Nip46Client / Nip46Bunker), a `signer_mode` field on every signing modes (Embedded / Nip46Client / Nip46Bunker), a `signer_mode` field on every
@ -28,6 +30,17 @@ tests travel with it.
replaces the old reveal modal. replaces the old reveal modal.
3. **Hash-chained audit log (a)** — SHA-256 hash-chained append-only log with atomic 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. 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 ### Security properties confirmed
- No secret material is logged or returned except the single, authenticated, audited - 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) ## Commits added this session (newest first)
| Hash | Message | | Hash | Message |
|------|---------| |------|---------|
| `6eff510` | feat: fail-closed ExportSecretKey with fresh auth and audit | | `114b343` | fix(packaging): restore Linux app icon so dist builds ship an icon |
| `2c61830` | feat: add per-profile signer modes with persisted NIP-46 connections |
| `caed722` | feat: add hash-chained, append-only audit log |
Parent of this session: `4038e2d` ("checkpoint: document embedded signer and NIP-46 (The prior session's three feature commits and their checkpoint `d92371f` remain the
client signer modes"). parent chain: `6eff510` → `2c61830` → `caed722` → … → `d92371f` → `114b343`.)
### Files touched by the three commits (30 files, +3921 / -611) ## Verification (run this session)
``` Full suite per AGENTS.md, run on top of `114b343` (icon is a binary asset, no code
Cargo.lock Cargo.toml frontend/electron/main.ts frontend/package.json change, so the suite is expected to hold):
frontend/package-lock.json frontend/src/components/ExportSecretKeyModal.tsx (new) - **Rust**: `cargo test --release` → **186 passed**, 0 failed; `cargo clippy
frontend/src/components/ShowSecretKeyModal.tsx (deleted) --all-targets` → clean (exit 0); `cargo fmt --check` → clean (exit 0); `cargo build
frontend/src/lib/api.ts frontend/src/lib/signer/SignerManager.ts (new) --release` → Finished, exit 0.
frontend/src/lib/types.ts frontend/src/screens/ProfilesScreen.tsx - **Frontend**: `npm test` → **116 passed** (16 files); `npm run typecheck` → clean
frontend/src/screens/SignerModeScreen.tsx frontend/src/state/AppProvider.tsx (exit 0); `npm run lint` → clean (exit 0).
frontend/src/styles.css frontend/src/test/apiMock.ts - `npm run format:check` → **exit 1** on 5 files (`src/components/ExportSecretKeyModal.tsx`,
frontend/src/test/ExportSecretKey.test.tsx (new) frontend/src/test/fakeBackend.ts `src/screens/SignerModeScreen.tsx`, `src/state/AppProvider.tsx`,
frontend/src/test/ShowSecretKey.test.tsx (deleted) `src/test/ExportSecretKey.test.tsx`, `src/test/fakeBackend.ts`). **Pre-existing** —
src/app.rs src/audit.rs (new) src/errors.rs src/ipc.rs src/lib.rs src/main.rs those files are committed as-is at `6eff510` (prior session) and are clean in the
src/profiles.rs src/signer/mod.rs src/signer/nip46_client.rs working tree; this icon-only change touched none of them. Left for the Step-7
src/signer/permissions.rs (new) src/signer/types.rs src/vault.rs 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
## Verification (run this session, per commit) `linux.target` (AppImage + deb) was also built directly to prove the icon lands:
Each commit was verified **in isolation** (checked out on top of its parent), not just - `release/Keynctr-0.1.0.AppImage` — 135,749,547 bytes.
as the final tree: - `release/keynectr_0.1.0_amd64.deb` — 105,810,972 bytes.
- `caed722` (audit): `cargo test --release` → **124 passed** (119 prior + 5 audit). - **Icon proven inside both artifacts**: the embedded icon is **byte-identical
- `2c61830` (signer modes): `cargo test --release` → **186 passed**; `cargo clippy (md5 `b3e372f7`)** to the generated `build/icon.png` in all four locations checked —
--all-targets` clean; `cargo fmt --check` clean; frontend `tsc --noEmit` clean; the deb's `usr/share/icons/hicolor/512x512/apps/keynectr.png`, the AppImage's hicolor
`npx vitest run` → **110 passed**. png, the AppImage's `.DirIcon`, and `build/icon.png` itself — all 512x512 RGBA with
- `6eff510` (export): `cargo test --release` → **186 passed**; frontend `tsc --noEmit` real transparency (32,626 opaque px, 226,582 transparent, corners alpha 0), ink
clean; `npx vitest run` → **116 passed** (110 + 6 export tests). pure black. The deb `.desktop` reads `Icon=keynectr`, matching the hicolor name.
## How to reproduce / exercise ## How to reproduce / exercise
- Backend: `cargo run --release -- serve` (JSON-lines IPC on stdio) or the CLI in - Backend: `cargo run --release -- serve` (JSON-lines IPC on stdio) or the CLI in
`src/main.rs`. `src/main.rs`.
- GUI: from `frontend/`, `npm run electron:build && electron .` (prod) or `npm run - GUI: from `frontend/`, `npm run electron:build && electron .` (prod) or `npm run
start:dev` with `NOSTR_GUI_DEV_URL`. 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 - 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. 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 - Exercise modes: Signer Mode screen → pick Embedded / NIP-46 client; connect a
`nostrconnect://` URI with a `perms=` parameter to grant scoped permissions. `nostrconnect://` URI with a `perms=` parameter to grant scoped permissions.
## Still uncommitted (do NOT lose; do NOT commit the hygiene junk) ## Still untracked (do NOT lose; do NOT commit the hygiene junk)
- **`frontend/build/icon.png` is deleted** (working tree) — the electron-builder Linux - **Source JPEG** `KeynectrAppIconPossibility02.jpeg` — intentionally **untracked**
icon. This is the Step-2 packaging fix: regenerate it from (the icon is now derived from it; the JPEG itself is not a build input and stays out
`KeynectrAppIconPossibility02.jpeg` (or point electron-builder at the new asset), of git, per instruction).
verify `npm run dist`, then commit. **Not done in this session.** - Pre-existing untracked hygiene leftovers (out of scope, address in the Step-7 hygiene
- **`CHECKPOINT-encryption.md`** — this file, committed to reference the post-triage pass): `COSMIC_THEME.md`, `.opencode/`, `.impeccable/`, `.directory`.
HEAD (`6eff510`). It must be re-updated to reference the new HEAD once Step 2 - **`frontend/build/icon.png` is no longer a leftover** — it was regenerated and
(packaging) lands its own commit. committed in `114b343` this session. The prior "deleted icon" item is closed.
- Pre-existing untracked hygiene leftovers (out of scope, address in the hygiene pass): - Build artifacts `release/` and `dist/` are gitignored and not committed.
`COSMIC_THEME.md`, `KeynectrAppIconPossibility02.jpeg`, `.opencode/`, `.impeccable/`, - `profiles_vault.json*` and `target/` remain correctly untracked and uncommitted
`.directory`. **`profiles_vault.json*` and `target/` remain correctly untracked and (vault is gitignored).
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) ## Deferred / next steps (unchanged, plus new)
- **Step 2 (packaging)**: restore `frontend/build/icon.png`, verify `npm run dist`. - **Step 2 (packaging): DONE.** Icon restored, `npm run dist` + declared targets green,
- Signer abstraction (Step 3): promote `src/signer` to the single `Signer` source of icon proven inside both artifacts, committed as `114b343`.
truth; introduce `SigningBackend { Internal{..}, Remote{..} }` per profile and route - **Step 3 (signer abstraction)** — proposed for sign-off, NOT yet implemented. Current
every IPC handler through the trait (no inline mode branching). 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 - 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 approval modal so grants (kinds, relays, expiry, rate) are persisted AND enforced in
the UI, not just parsed. the UI, not just parsed.
@ -113,8 +140,10 @@ as the final tree:
+ backward-compatible migration; `--allow-env-secret` flag; remove or gate deprecated + backward-compatible migration; `--allow-env-secret` flag; remove or gate deprecated
`RevealSecretKey` IPC behind the same fail-closed path. `RevealSecretKey` IPC behind the same fail-closed path.
- Undo history (Step 6): resolve the `ProfileSummary`-loses-the-secret question. - Undo history (Step 6): resolve the `ProfileSummary`-loses-the-secret question.
- Hygiene (Step 7): Keynctr rename pass, delete legacy Python, migrate root - Hygiene (Step 7): Keynctr rename pass (includes the `homepage` → correct repo fix noted
`profiles_vault.json*` into `~/.local/share/keynectr`. 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 - 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. 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. Harmless (idempotent) but wasteful; tighten to only report real changes.