diff --git a/CHECKPOINT-encryption.md b/CHECKPOINT-encryption.md
index f957c83..2271d5c 100644
--- a/CHECKPOINT-encryption.md
+++ b/CHECKPOINT-encryption.md
@@ -1,80 +1,3 @@
-# Checkpoint — stdin EAGAIN fix, accent theme, identity backfill (2026-09-28 evening)
-
-## Where things are
-- Project: `/home/avi/Projects/Keynctr`, branch `master` @ **`fac4ba3`**
- ("feat(desktop): launch from the applications list (folio-style)").
- Previous: `d792a72` (Archipelago uses the reference artwork as canvas),
- `4d0df31` (Archipelago theme tokens, first pass). `5b60ae3` (persistent identity backfill for generic pairing
- labels), `a6182e3` (iOS blue accent for Workshop Dark + custom accent
- setting), `9770f46` (stdin blocking-thread reader — EAGAIN crash fix),
- `6bff188` (docs), `dcc701f` (no-restart self-update).
-- Working tree: clean for tracked files. Untracked intentionally NOT
- committed: `COSMIC_THEME.md`, `KeynectrAppIconPossibility02.jpeg`,
- `deferred/SignerConnectionPanel.tsx.wip/`.
-- Release binary rebuilt 2026-09-28 ~12:40 (from the 4d0df31 tree).
-- origin/master is at `a6182e3`; local is 1 commit ahead — push still blocked
- (no Forgejo token stored for this session; HTTPS asks for credentials).
-
-## Pairing problem status — RESOLVED (live evidence)
-- `~/.local/share/keynectr/profiles_vault.json` now carries **3 persisted
- `nip46_connections`** rows with matching `signer_mode: nip46_client` profile
- rows (created Sep 25, Sep 27, and **Sep 28 10:23**). The Sep 28 pairing
- happened on the current build: Amber's connect event parses, the handshake
- completes, and the profile row persists — the original
- "Amber says connected but no row appears" failure no longer reproduces.
-- The Sep 28 row is the **active profile**. Its label is still the generic
- "Amber" because the paired account has not published kind-0 yet; `5b60ae3`
- backfill re-checks every ~2-5 min while the backend runs and will rename it
- when metadata appears. No further parse-side work outstanding.
-
-## What was completed (this checkpoint's commits)
-1. **`9770f46` stdin EAGAIN crash fix** — `keynectr serve` treated a transient
- EAGAIN on the Electron pipe as fatal ("Rust backend exited unexpectedly
- code 1"). Dedicated blocking-thread reader + mpsc; verified live with a
- 200-line request stream.
-2. **`a6182e3` custom accent color** — iOS blue accent for Workshop Dark plus
- a user-settable accent color.
-3. **`5b60ae3` persistent identity backfill** — slow-cadence loop in `serve()`
- re-fetches kind-0 for Nip46Client rows still wearing generic
- "Amber"/"Remote Signer" placeholders; user renames never overwritten;
- locked vaults skipped; network off-lock, 90 s budget per identity.
-4. **`4d0df31` Archipelago theme** — new `archipelago` theme from the user's
- synthwave reference art: teal-navy dusk canvas, coral (#f0685c) scanline
- sun glow + pink cloud banks + faint horizontal scanlines on `.main`
- (fixed attachment), glassmorphic blur on cards/sidebar, white logo filter.
- Settings -> Appearance -> "Archipelago - Sunset Sea".
-5. **`d792a72` Archipelago artwork canvas (v2)** — first pass was abstract
- washes and did not match the reference; replaced with the actual
- illustration (`frontend/public/archipelago-bg.jpg`) cover/fixed under a
- dark scrim on `.main`, translucent blur-glass cards + sidebar, light
- hairline borders, coral accent. This is what the reference mock shows.
-6. **`fac4ba3` applications-list launcher** — `launch-keynctr.sh` +
- `~/.local/share/applications/keynctr.desktop` (validated); GPU env set
- for Hyprland; single-instance lock focuses the existing window.
- Verified by gtk-launch twice: one window, correct WM class.
-
-## Verification (2026-09-28 evening cron pass)
-- `cargo test` → 221 unit + 6 e2e passed, 0 failed.
-- `cargo clippy --all-targets` → 0 warnings. `cargo fmt --check` → clean.
-- `frontend`: `npm test` → 139 passed (19 files); `typecheck`, `lint`,
- `format:check` all clean.
-
-## How to resume
-- GUI: `cd ~/Projects/Keynctr/frontend && npx vite --port 5173` then
- `NOSTR_GUI_DEV_URL=http://localhost:5173 KEYNCTR_ENABLE_GPU=1 npx electron .`
-- CLI backend: `target/release/keynectr serve` (spawned by Electron).
-- Push pending commit when credentials are available: `git push origin master`.
-
-## Outstanding / next steps
-- Live confirmation of the backfill rename: pair/keep a fresh Amber account
- with no kind-0, publish kind-0 from Amber, confirm the Keynctr row renames
- itself within ~5 min without restarting the app.
-- Step 4 permissions UI follow-ups, Step 5 KDF, Step 7 rename/hygiene
- (unchanged from previous checkpoint).
-- `package.json` `homepage` still points at github.com/avi/Keynctr (Step 7).
-
----
-
# Checkpoint — permissions UI + updater fix + Workshop theme (2026-09-27 night)
## Where things are
diff --git a/frontend/electron/main.ts b/frontend/electron/main.ts
index 589dc87..9af5e94 100644
--- a/frontend/electron/main.ts
+++ b/frontend/electron/main.ts
@@ -1002,24 +1002,6 @@ if (process.platform === 'linux') {
app.setDesktopName('keynectr.desktop');
}
-// Launched from the applications list a second time? Focus the existing
-// window instead of starting a duplicate app (two Electron shells would
-// each spawn their own backend and fight over the vault file).
-const gotInstanceLock = app.requestSingleInstanceLock();
-if (!gotInstanceLock) {
- // Hard exit: app.quit() is async and would let the rest of this module
- // (whenReady → backend spawn) run in the doomed second instance.
- app.exit(0);
-} else {
- app.on('second-instance', () => {
- const [window] = BrowserWindow.getAllWindows();
- if (window) {
- if (window.isMinimized()) window.restore();
- window.focus();
- }
- });
-}
-
app.whenReady().then(() => {
protocol.handle('app', (request) => {
const { pathname } = new URL(request.url);
diff --git a/frontend/public/archipelago-bg.jpg b/frontend/public/archipelago-bg.jpg
deleted file mode 100644
index 6051daf..0000000
Binary files a/frontend/public/archipelago-bg.jpg and /dev/null differ
diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts
index 9d71707..1b2db24 100644
--- a/frontend/src/lib/types.ts
+++ b/frontend/src/lib/types.ts
@@ -7,8 +7,7 @@ export type Theme =
| 'impeccable-dark'
| 'cosmic'
| 'workshop'
- | 'workshop-dark'
- | 'archipelago';
+ | 'workshop-dark';
/** Active signer mode. */
export type SignerMode = 'embedded' | 'nip46_bunker' | 'nip46_client';
diff --git a/frontend/src/screens/SettingsScreen.tsx b/frontend/src/screens/SettingsScreen.tsx
index 428c2ee..fdfd396 100644
--- a/frontend/src/screens/SettingsScreen.tsx
+++ b/frontend/src/screens/SettingsScreen.tsx
@@ -148,7 +148,6 @@ export function SettingsScreen() {
-
/dev/null 2>&1 || npm run build
- npm run electron:build >/dev/null 2>&1 || npm run electron:build
-fi
-
-# On this Hyprland machine native-Wayland rendering needs hardware GL:
-# software rendering dies with "GPU process isn't usable" (verified
-# 2026-09-30). Same setting the dev workflow uses.
-export KEYNCTR_ENABLE_GPU=1
-
-# --class pins WM_CLASS so the launcher icon groups with the window
-# (StartupWMClass=keynectr in the .desktop file).
-exec "$APP_DIR/node_modules/.bin/electron" --class=keynectr "$APP_DIR" "$@"
diff --git a/src/app.rs b/src/app.rs
index 5460e89..8ee1134 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -169,16 +169,8 @@ impl App {
self.signing_for(&npub).await
}
- /// Verify a password, keep the derived key in memory for the session,
- /// and transparently upgrade the vault's KDF if it is still encrypted
- /// under the legacy parameters (19 MiB / t=2).
- ///
- /// Returns `true` when the vault was re-wrapped under the current
- /// [`crate::crypto::KDF_M_COST`]/[`crate::crypto::KDF_T_COST`] (the caller
- /// should persist the vault), `false` for a plain unlock. A wrong password
- /// never touches the vault header: the upgrade only runs after the old
- /// verifier has accepted the supplied password.
- pub fn unlock(&mut self, password: &str) -> Result {
+ /// Verify a password and keep the derived key in memory for the session.
+ pub fn unlock(&mut self, password: &str) -> Result<(), AppError> {
let crypto = self
.vault
.crypto
@@ -191,41 +183,8 @@ impl App {
key.zeroize();
return Err(AppError::wrong_password());
}
-
- let is_legacy = crypto.kdf.m_cost == crypto::LEGACY_KDF_M_COST
- && crypto.kdf.t_cost == crypto::LEGACY_KDF_T_COST;
- if !is_legacy {
- self.unlock_key = Some(key);
- return Ok(false);
- }
-
- // Password accepted under the legacy parameters: re-derive with the
- // current ones and re-wrap every stored secret under the new key.
- let upgraded = {
- let salt = crypto::generate_salt()?;
- let new_key = crypto::derive_key(
- password,
- &salt,
- crypto::KDF_M_COST,
- crypto::KDF_T_COST,
- crypto::KDF_P_COST,
- )?;
- rewrap_secrets(&mut self.vault, Some(&key), &new_key)?;
- key.zeroize();
- self.vault.crypto = Some(VaultCrypto {
- kdf: KdfParams {
- algorithm: "argon2id".to_string(),
- salt: B64.encode(salt),
- m_cost: crypto::KDF_M_COST,
- t_cost: crypto::KDF_T_COST,
- p_cost: crypto::KDF_P_COST,
- },
- verifier: crypto::make_verifier(&new_key)?,
- });
- new_key
- };
- self.unlock_key = Some(upgraded);
- Ok(true)
+ self.unlock_key = Some(key);
+ Ok(())
}
/// Drop the derived key, re-locking the vault for the session.
@@ -389,14 +348,25 @@ impl App {
crypto::KDF_P_COST,
)?;
- // Re-wrap every secret store (profiles, connection secrets, client
- // keys) so no credential keeps its old wrapping.
- rewrap_secrets(&mut self.vault, previous_key.as_ref(), &new_key)?;
- // Wipe the old vault key now that everything is re-encrypted.
+ let mut encrypted = Vec::with_capacity(self.vault.profiles.len());
+ for profile in &self.vault.profiles {
+ // Decrypted plaintexts are Zeroizing: each wipes itself once the
+ // re-encrypted replacement has been produced.
+ let plaintext = match &previous_key {
+ Some(key) => crypto::decrypt_secret(key, &profile.secret_key)?,
+ None => Zeroizing::new(profile.secret_key.clone()),
+ };
+ encrypted.push(StoredProfile {
+ secret_key: crypto::encrypt_secret(&new_key, &plaintext)?,
+ ..profile.clone()
+ });
+ }
+ // Wipe the old vault key now that every profile is re-encrypted.
if let Some(mut key) = previous_key {
key.zeroize();
}
+ self.vault.profiles = encrypted;
self.vault.crypto = Some(VaultCrypto {
kdf: KdfParams {
algorithm: "argon2id".to_string(),
@@ -497,47 +467,6 @@ fn derive_with(crypto: &VaultCrypto, password: &str) -> Result,
- new_key: &VaultKey,
-) -> Result<(), AppError> {
- let decrypt_one = |blob: &str| -> Result, AppError> {
- match old_key {
- Some(key) => crypto::decrypt_secret(key, blob),
- None => Ok(Zeroizing::new(blob.to_string())),
- }
- };
-
- let mut profiles = Vec::with_capacity(vault.profiles.len());
- for profile in &vault.profiles {
- let plaintext = decrypt_one(&profile.secret_key)?;
- profiles.push(StoredProfile {
- secret_key: crypto::encrypt_secret(new_key, &plaintext)?,
- ..profile.clone()
- });
- }
- vault.profiles = profiles;
-
- for entry in &mut vault.connection_secrets {
- let plaintext = decrypt_one(&entry.secret)?;
- entry.secret = crypto::encrypt_secret(new_key, &plaintext)?;
- }
- for entry in &mut vault.connection_client_keys {
- let plaintext = decrypt_one(&entry.secret_hex)?;
- entry.secret_hex = crypto::encrypt_secret(new_key, &plaintext)?;
- }
- Ok(())
-}
-
#[cfg(test)]
mod tests {
use super::*;
@@ -678,138 +607,6 @@ mod tests {
assert_eq!(err.kind(), ErrorKind::Config);
}
- /// Build a vault encrypted under explicit (legacy) KDF parameters,
- /// exactly as an older app version would have written it, and return the
- /// matching password. Also stores one connection secret and one client
- /// key encrypted under the same legacy key.
- fn legacy_kdf_app(m_cost: u32, t_cost: u32) -> App {
- use crate::crypto;
- let password = "correct horse battery staple";
- let salt = crypto::generate_salt().unwrap();
- let key = crypto::derive_key(password, &salt, m_cost, t_cost, crypto::KDF_P_COST).unwrap();
- let mut app = sample_app();
- let ref_ = crate::signer::VaultRef::new(Some("npub1test".to_string()), "aabb".to_string());
- app.vault.crypto = Some(VaultCrypto {
- kdf: KdfParams {
- algorithm: "argon2id".to_string(),
- salt: B64.encode(salt),
- m_cost,
- t_cost,
- p_cost: crypto::KDF_P_COST,
- },
- verifier: crypto::make_verifier(&key).unwrap(),
- });
- // crypto is set first so these store ENCRYPTED under the legacy key,
- // exactly as a running pre-upgrade app would have written them.
- for profile in &mut app.vault.profiles {
- profile.secret_key = crypto::encrypt_secret(&key, &profile.secret_key).unwrap();
- }
- vault::store_connection_secret(&mut app.vault, Some(&key), &ref_, "pairing-secret")
- .unwrap();
- vault::store_connection_client_key(
- &mut app.vault,
- Some(&key),
- &ref_,
- "cd".repeat(32).as_str(),
- )
- .unwrap();
- app
- }
-
- #[test]
- fn unlock_upgrades_legacy_kdf_params() {
- let mut app = legacy_kdf_app(crypto::LEGACY_KDF_M_COST, crypto::LEGACY_KDF_T_COST);
- let legacy_m = app.vault.crypto.as_ref().unwrap().kdf.m_cost;
- assert_ne!(legacy_m, crypto::KDF_M_COST, "test vault must be legacy");
-
- let upgraded = app
- .unlock("correct horse battery staple")
- .expect("legacy vault must unlock");
- assert!(upgraded, "unlock must report a KDF upgrade happened");
-
- let kdf = &app.vault.crypto.as_ref().unwrap().kdf;
- assert_eq!(kdf.m_cost, crypto::KDF_M_COST);
- assert_eq!(kdf.t_cost, crypto::KDF_T_COST);
-
- // The new key must decrypt every profile secret re-wrapped under it,
- // and the new verifier must accept it.
- let key = app.vault_key().expect("unlocked");
- assert!(crypto::verify(
- key,
- &app.vault.crypto.as_ref().unwrap().verifier
- ));
- let secret = profiles::resolve_secret_key(
- &app.vault,
- &app.vault.profiles[0].public_key.clone(),
- app.vault_key(),
- )
- .unwrap();
- assert!(!secret.is_empty());
-
- // Unlocking again is a no-op: already at current parameters.
- app.lock();
- assert!(
- !app.unlock("correct horse battery staple").unwrap(),
- "current-params vault must not re-upgrade"
- );
- }
-
- #[test]
- fn kdf_upgrade_rewraps_connection_secrets_and_client_keys() {
- let mut app = legacy_kdf_app(crypto::LEGACY_KDF_M_COST, crypto::LEGACY_KDF_T_COST);
- let ref_ = crate::signer::VaultRef::new(Some("npub1test".to_string()), "aabb".to_string());
- app.unlock("correct horse battery staple").unwrap();
-
- let secret = vault::resolve_connection_secret(&app.vault, app.vault_key(), &ref_)
- .unwrap()
- .expect("connection secret survives the upgrade");
- assert_eq!(secret.as_str(), "pairing-secret");
- let client_key = vault::resolve_connection_client_key(&app.vault, app.vault_key(), &ref_)
- .unwrap()
- .expect("client key survives the upgrade");
- assert_eq!(client_key.as_str(), "cd".repeat(32));
- }
-
- #[test]
- fn wrong_password_does_not_upgrade_kdf() {
- let mut app = legacy_kdf_app(crypto::LEGACY_KDF_M_COST, crypto::LEGACY_KDF_T_COST);
- let err = app
- .unlock("not the password")
- .expect_err("wrong password must fail");
- assert_eq!(err.kind(), ErrorKind::WrongPassword);
- let kdf = &app.vault.crypto.as_ref().unwrap().kdf;
- assert_eq!(kdf.m_cost, crypto::LEGACY_KDF_M_COST, "header untouched");
- assert!(app.is_locked());
- }
-
- #[test]
- fn set_password_rewraps_connection_secrets_and_client_keys() {
- // Plaintext vault with connection credentials: setting a password
- // must encrypt EVERY secret, not just profile keys.
- let mut app = sample_app();
- let ref_ = crate::signer::VaultRef::new(Some("npub1test".to_string()), "aabb".to_string());
- vault::store_connection_secret(&mut app.vault, None, &ref_, "pairing-secret").unwrap();
- vault::store_connection_client_key(&mut app.vault, None, &ref_, "ef".repeat(32).as_str())
- .unwrap();
-
- app.set_password(None, "correct horse battery staple")
- .unwrap();
-
- let serialized = serde_json::to_string(&app.vault).unwrap();
- assert!(
- !serialized.contains("pairing-secret"),
- "pairing secret must not survive in plaintext"
- );
- assert!(
- !serialized.contains(&"ef".repeat(32)),
- "client key must not survive in plaintext"
- );
- let secret = vault::resolve_connection_secret(&app.vault, app.vault_key(), &ref_)
- .unwrap()
- .expect("secret still resolvable after encryption");
- assert_eq!(secret.as_str(), "pairing-secret");
- }
-
#[test]
fn unlock_roundtrip_with_wrong_then_right_password() {
let mut app = sample_app();
diff --git a/src/crypto.rs b/src/crypto.rs
index 21f1181..e58bed4 100644
--- a/src/crypto.rs
+++ b/src/crypto.rs
@@ -22,22 +22,13 @@ pub const SALT_LEN: usize = 16;
/// AES-GCM nonce length in bytes.
pub const NONCE_LEN: usize = 12;
-/// Argon2id memory cost in KiB (64 MiB — OWASP 2024 recommendation).
-pub const KDF_M_COST: u32 = 64 * 1024;
+/// Argon2id memory cost in KiB (RFC 9106 recommendation).
+pub const KDF_M_COST: u32 = 19 * 1024;
/// Argon2id time cost (iterations).
-pub const KDF_T_COST: u32 = 3;
+pub const KDF_T_COST: u32 = 2;
/// Argon2id parallelism.
pub const KDF_P_COST: u32 = 1;
-/// Memory cost used by app versions before the Step 5 upgrade (RFC 9106's
-/// 19 MiB / t=2). Vaults carrying exactly these parameters are transparently
-/// re-wrapped under [`KDF_M_COST`]/[`KDF_T_COST`] on next unlock; vaults with
-/// any OTHER parameters keep them — the header is authoritative, and these
-/// constants are only the definition of "legacy".
-pub const LEGACY_KDF_M_COST: u32 = 19 * 1024;
-/// Time cost used by pre-upgrade versions (see [`LEGACY_KDF_M_COST`]).
-pub const LEGACY_KDF_T_COST: u32 = 2;
-
/// The in-memory key that unlocks an encrypted vault.
pub type VaultKey = [u8; KEY_LEN];
diff --git a/src/ipc.rs b/src/ipc.rs
index 43de978..dcb07a1 100644
--- a/src/ipc.rs
+++ b/src/ipc.rs
@@ -5,8 +5,6 @@ use serde::{Deserialize, Serialize};
use serde_json::json;
use tokio::sync::Mutex;
-use nostr_sdk::prelude::PublicKey;
-
use crate::app::{App, Nip46ClientSignerHandle};
use crate::errors::AppError;
use crate::feed;
@@ -333,107 +331,6 @@ pub async fn serve() -> Result<(), AppError> {
let mut tasks = JoinSet::new();
- // Persistent identity backfill. Pairing-time enrichment tries for ~2
- // minutes and stops; if the account's kind-0 was never published (or was
- // published later), the row would keep its generic "Amber"/"Remote
- // Signer" placeholder forever — which is what a new user with a fresh
- // Amber account sees, with no way to know the app resolved nothing.
- // This loop retries on a slow cadence while the backend runs: every pass
- // collects remote rows still wearing a generic placeholder (only once
- // the vault is unlocked — an encrypted vault hides them otherwise),
- // fetches kind-0 from the enabled relays, and upgrades the row. A user
- // rename always wins: is_generic_pairing_label is the sole gate on the
- // label write. Cheap when nothing is pending (one vault scan per pass).
- {
- let app = app.clone();
- tasks.spawn(async move {
- loop {
- // Collect pending identities without holding the lock
- // across network work.
- let pending: Vec<(String, PublicKey)> = {
- let guard = app.lock().await;
- if guard.is_locked() {
- Vec::new()
- } else {
- guard
- .vault
- .profiles
- .iter()
- .filter(|p| {
- p.signer_mode == SignerMode::Nip46Client
- && profiles::is_generic_pairing_label(&p.label)
- })
- .filter_map(|p| {
- PublicKey::parse(&p.public_key)
- .ok()
- .map(|key| (p.public_key.clone(), key))
- })
- .collect()
- }
- };
- if pending.is_empty() {
- tokio::time::sleep(Duration::from_secs(300)).await;
- continue;
- }
- let relay_urls = {
- let guard = app.lock().await;
- relays::enabled_urls(&guard.settings)
- };
- for (npub, identity) in pending {
- let found = tokio::time::timeout(
- Duration::from_secs(90),
- tokio::task::spawn_blocking({
- let relay_urls = relay_urls.clone();
- move || profiles::fetch_profile_metadata(&identity, &relay_urls)
- }),
- )
- .await
- .ok()
- .and_then(|join| join.ok())
- .flatten();
- let Some(meta) = found else { continue };
- let mut guard = app.lock().await;
- let mut changed = false;
- // Re-resolve by pubkey string: another request (rename,
- // a pairing-time enrichment) may have edited the row
- // while this fetch was in flight.
- if let Some(row) = guard
- .vault
- .profiles
- .iter_mut()
- .find(|p| p.public_key == npub)
- {
- if row.picture.is_none() && meta.picture.is_some() {
- row.picture = meta.picture.clone();
- changed = true;
- }
- if row.nip05.is_none() && meta.nip05.is_some() {
- row.nip05 = meta.nip05.clone();
- changed = true;
- }
- let real_name = meta
- .display_name
- .as_deref()
- .or(meta.name.as_deref())
- .map(str::trim)
- .filter(|name| !name.is_empty());
- if let Some(name) = real_name {
- if profiles::is_generic_pairing_label(&row.label) {
- row.label = name.to_string();
- changed = true;
- }
- }
- }
- if changed {
- let _ = guard.save_vault();
- eprintln!("[backfill] resolved profile identity via kind-0: {npub}");
- }
- }
- tokio::time::sleep(Duration::from_secs(120)).await;
- }
- });
- }
-
while let Some(line) = line_rx.recv().await {
if line.trim().is_empty() {
continue;
@@ -1024,12 +921,7 @@ async fn run_with_app(app: &mut App, request: Request) -> Result {
- let upgraded = app.unlock(&password)?;
- if upgraded {
- // The vault was transparently re-wrapped under stronger KDF
- // parameters: persist immediately so the upgrade sticks.
- app.save_vault()?;
- }
+ app.unlock(&password)?;
// Re-initialize signers with unlocked vault
if app.signer_mode == SignerMode::Embedded {
if let Some(signer) = &app.embedded_signer {
diff --git a/src/main.rs b/src/main.rs
index 4bb8530..525b731 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -483,10 +483,7 @@ fn load_app_with_unlock() -> Result {
let mut app = App::load()?;
if app.is_locked() {
let password = prompt_password("Vault password: ")?;
- if app.unlock(&password)? {
- // Transparent KDF upgrade: persist the re-wrapped vault.
- app.save_vault()?;
- }
+ app.unlock(&password)?;
}
Ok(app)
}
@@ -534,12 +531,7 @@ fn cli_unlock() -> Result {
return Err(AppError::config("Your vault is not encrypted."));
}
let password = prompt_password("Vault password: ")?;
- if app.unlock(&password)? {
- app.save_vault()?;
- return Ok(
- "Vault unlocked and upgraded to stronger key-derivation parameters.".to_string(),
- );
- }
+ app.unlock(&password)?;
Ok("Vault unlocked.".to_string())
}
diff --git a/src/profiles.rs b/src/profiles.rs
index 2da0bf1..d5b8dfc 100644
--- a/src/profiles.rs
+++ b/src/profiles.rs
@@ -553,19 +553,6 @@ pub fn find_stored_profile<'a>(
.ok_or_else(|| AppError::profile_not_found(npub))
}
-/// Labels the UI assigns when pairing without a user-supplied name
-/// (CreateProfileModal defaults to "Amber"; Signer Mode to "Remote
-/// Signer"). A remote-signer profile row still carrying one of these has
-/// never resolved its real identity from the network, so it stays a
-/// candidate for automatic name backfill on every launch. A user rename
-/// always falls outside this list and is therefore never overwritten.
-pub const GENERIC_PAIRING_LABELS: &[&str] = &["Amber", "Remote Signer"];
-
-/// True when `label` is one of the generic pairing placeholders.
-pub fn is_generic_pairing_label(label: &str) -> bool {
- GENERIC_PAIRING_LABELS.contains(&label)
-}
-
/// Create or refresh the vault profile for a remote (NIP-46) identity.
///
/// When a NIP-46 client connection is established the identity lives on the
@@ -943,20 +930,6 @@ pub fn delete_profile(vault: &mut Vault, npub: &str) -> Result Vault {
diff --git a/src/settings.rs b/src/settings.rs
index bf6176c..9f96f99 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -35,10 +35,6 @@ pub enum Theme {
/// personality change") — near-black paper, warm light ink, pale pine.
#[serde(rename = "workshop-dark")]
WorkshopDark,
- /// "Archipelago": synthwave sunset sea from the user's reference art —
- /// teal-navy dusk, coral scanline sun, glass-island cards.
- #[serde(rename = "archipelago")]
- Archipelago,
}
impl Theme {
@@ -54,7 +50,6 @@ impl Theme {
"cosmic" => Some(Self::Cosmic),
"workshop" => Some(Self::Workshop),
"workshop-dark" => Some(Self::WorkshopDark),
- "archipelago" => Some(Self::Archipelago),
_ => None,
}
}