From 332ab647c9b9be1655439837feea2872ef10d8e3 Mon Sep 17 00:00:00 2001 From: Avi Date: Sat, 26 Sep 2026 20:34:08 -0500 Subject: [PATCH 01/17] fix(nip46): gate remaining trace writes to live relay sessions The wrong-identity refusal test and the auto-name retry loop still wrote into the live pairing-trace.log on every e2e run: three bogus "restored signer answered as a different account" npubs and phantom "auto-name attempt" lines were test traffic, not live Amber misbehaviour, and they kept derailing the forensics review. fail and the background enrichment traces now check live_relays like every other trace site. Verified: running the e2e suite appends zero lines to the trace file. --- src/signer/nip46_client.rs | 58 +++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/src/signer/nip46_client.rs b/src/signer/nip46_client.rs index 44ddf95..0d6ab3d 100644 --- a/src/signer/nip46_client.rs +++ b/src/signer/nip46_client.rs @@ -1305,7 +1305,22 @@ impl Nip46ClientSigner { let message = message.into(); eprintln!("[nip46] session failed: {message}"); eprintln!("[NIP46] session failed: {message}"); - pairing_trace(&format!("session failed: {message}")); + // Forensics-log only LIVE sessions. The e2e harness deliberately + // fails restored sessions (wrong-identity refusal test), and its + // "session failed" lines were landing in pairing-trace.log among + // real ones — three different bogus "restored signer answered as a + // different account" npubs turned out to be test runs, not live + // Amber misbehaviour. Read the relays under a short lock before the + // mutating one below. + let relays = self + .inner + .try_lock() + .ok() + .and_then(|g| g.connection.as_ref().map(|c| c.relays.clone())) + .unwrap_or_default(); + if live_relays(&relays) { + pairing_trace(&format!("session failed: {message}")); + } if let Ok(mut inner) = self.inner.try_lock() { // First failure wins: the demux loop exits with a generic // "Connect handshake failed" AFTER the handshake task already @@ -2328,6 +2343,7 @@ impl Nip46ClientSigner { // UI polls status and vault, so the row fills in a moment later. { let app = self.app.clone(); + let live_enrichment = live_relays(&connection.relays); tokio::spawn(async move { let relays_for_meta = { let app = app.lock().await; @@ -2358,15 +2374,27 @@ impl Nip46ClientSigner { meta = Some(found); break; } - Ok(Ok(None)) => pairing_trace(&format!( - "auto-name attempt {attempt}: no kind-0 found on any relay" - )), - Ok(Err(join_err)) => pairing_trace(&format!( - "auto-name attempt {attempt}: fetch task panicked: {join_err}" - )), - Err(_) => pairing_trace(&format!( - "auto-name attempt {attempt}: fetch timed out (75s budget)" - )), + Ok(Ok(None)) => { + if live_enrichment { + pairing_trace(&format!( + "auto-name attempt {attempt}: no kind-0 found on any relay" + )); + } + } + Ok(Err(join_err)) => { + if live_enrichment { + pairing_trace(&format!( + "auto-name attempt {attempt}: fetch task panicked: {join_err}" + )); + } + } + Err(_) => { + if live_enrichment { + pairing_trace(&format!( + "auto-name attempt {attempt}: fetch timed out (75s budget)" + )); + } + } } if attempt < 4 { tokio::time::sleep(Duration::from_secs(20)).await; @@ -2376,10 +2404,12 @@ impl Nip46ClientSigner { Some(meta) => meta, None => return, }; - pairing_trace(&format!( - "auto-name: kind-0 fetched (display_name={:?} name={:?})", - meta.display_name, meta.name - )); + if live_enrichment { + pairing_trace(&format!( + "auto-name: kind-0 fetched (display_name={:?} name={:?})", + meta.display_name, meta.name + )); + } let mut app = app.lock().await; let mut changed = false; if let Some(row) = app From 1b4655c07bbc7d9a15a730bcba4ce3638eb8d7c1 Mon Sep 17 00:00:00 2001 From: Avi Date: Sat, 26 Sep 2026 20:37:21 -0500 Subject: [PATCH 02/17] docs(checkpoint): forensics log cleaned + live publish confirmed at 332ab64 (2026-09-26) --- CHECKPOINT-encryption.md | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/CHECKPOINT-encryption.md b/CHECKPOINT-encryption.md index f4cb723..e88cb10 100644 --- a/CHECKPOINT-encryption.md +++ b/CHECKPOINT-encryption.md @@ -1,3 +1,58 @@ +# Checkpoint — forensics log cleaned + live publish confirmed (2026-09-26) + +## Where things are +- Project: `/home/avi/Projects/Keynctr` +- Branch: `master` @ **`332ab64`** ("fix(nip46): gate remaining trace + writes to live relay sessions"). Previous: `704addc` (checkpoint), + `bc736ff` (auto-name retry), `b5c61de`, `fc2fe93`. +- Working tree clean except the standing untracked files + (COSMIC_THEME.md, icon jpeg, deferred/). +- Release binary rebuilt at 332ab64 (mtime Sep 26 20:34, real 22s + compile, not a cache hit). + +## What was completed +1. **LIVE PUBLISH CONFIRMED (was the last open item)** — el.log shows + three sign_event responses ~19:50 Sep 25 and relay probes confirm + kind:1 notes (id 5191172d01f9…, fe9a256f597f…, text "test" + + image) from npub1qn0w4… accepted on primal/damus/snort/nos.lol at + exactly those timestamps. End-to-end Amber signing works. +2. **False alarms retired**: the repeated "restored signer answered as + a different account" and duplicate "auto-name attempt" lines in + pairing-trace.log were E2E TEST traffic (wrong-identity refusal test + + loopback auto-name loop), not live Amber failures — each bogus + npub appeared exactly at test-run times (17:09 rebuild, 20:07 suite). +3. **Trace gating fix (332ab64)**: `fail()` and the background + auto-name traces now check `live_relays()` like every other site. + Verified by measurement: e2e suite run leaves pairing-trace.log + byte-identical (was 240 lines before, 240 after). +4. **Live vault pruned (not in git)**: dropped the legacy `fac852dc…` + connection row (15:37 pairing, predates client-key persistence, + superseded by 19:35 `4148a9a1…` pairing) so startup restore can't + waste a re-dial on a keyless row. Backup: + profiles_vault.json.backup-cron-20260926. Done with backend down. + +## Verified this session +- cargo test: 216 unit + 5 e2e green. clippy --all-targets: 0 warnings. + cargo fmt --check clean. cargo build --release rebuilt at 332ab64. +- Frontend untouched this session (no npm run needed). +- Serve smoke test on the REAL vault (backend was down): startup + restore fires "restoring session: peer=4148a9a1… client + pubkey=64ea18e8…" (the persisted key from the 19:35 pairing), relays + connect, no errors. Full handshake needs Amber online — user test. +- kind-0 'web5osint' confirmed live on nos.lol; profile row already + carries the name + picture in the vault. + +## Outstanding / next user steps +- One scanless restart check: launch the GUI with Amber online and + watch for "identity check on restored session: PASS" without + scanning (restore now targets only the restorable 4148a9a1 row). +- Optional: publish kind-0 to primal/damus too so naming doesn't + depend on nos.lol alone (needs one Amber signature). +- reminder: pkill patterns matching their own launch string kill the + cron shell — resolve PID by full binary path first. + +--- + # Checkpoint — auto-naming hardened (2026-09-25 eve) ## What changed since the label-step checkpoint From c89b31aaf1047bc3a6484b62ea69b3e8c8de271b Mon Sep 17 00:00:00 2001 From: Avi Date: Sun, 27 Sep 2026 21:01:23 -0500 Subject: [PATCH 03/17] =?UTF-8?q?feat(nip46):=20pair=20a=20second=20signer?= =?UTF-8?q?=20account=20=E2=80=94=20park=20the=20live=20session,=20switch?= =?UTF-8?q?=20re-dials=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One live NIP-46 session, many saved ones (Option A): - start_pairing/connect while a session is live PARKS it instead of refusing: row, pairing secret, and persisted client key stay intact, so the parked account is restorable with no fresh scan. - SelectProfile follows the switch: target has a restorable connection -> park current + re-dial target's row (expected_identity guard applies); target is local-key or unpaired -> live session untouched. - New nip46_cancel_pairing IPC: aborts ONLY an in-flight pairing and re-dials the parked session, so cancel-after-park is transparent. The QR cancel paths (Add-profile modal, Signer Mode screen) use it — plain disconnect would revoke the parked connection. - e2e: two fake Ambers on one relay; A pairs, B's pairing parks A (revoked_at none, client key resolvable), switch back re-dials A and signs; no-op switch; local profile leaves session alone; B restorable. --- frontend/src/lib/api.ts | 1 + frontend/src/screens/CreateProfileModal.tsx | 10 +- frontend/src/screens/SignerModeScreen.tsx | 11 +- frontend/src/state/AppProvider.tsx | 4 + frontend/src/test/CreateProfileModal.test.tsx | 5 +- frontend/src/test/fakeBackend.ts | 8 + src/ipc.rs | 79 ++++-- src/signer/nip46_client.rs | 142 ++++++++++ tests/nip46_e2e.rs | 245 ++++++++++++++++++ 9 files changed, 482 insertions(+), 23 deletions(-) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 7d6fdb5..fc67375 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -121,6 +121,7 @@ export const api = { call('nip46_connect', { uri, label }), nip46PairStart: (label: string) => call('nip46_pair_start', { label }), nip46Disconnect: () => call('nip46_disconnect'), + nip46CancelPairing: () => call('nip46_cancel_pairing'), nip46Status: () => call('nip46_status'), nip46Approve: (id: string, approved: boolean, always = false) => call('nip46_approve', { id, approved, always }), diff --git a/frontend/src/screens/CreateProfileModal.tsx b/frontend/src/screens/CreateProfileModal.tsx index 7c259e5..db39ef7 100644 --- a/frontend/src/screens/CreateProfileModal.tsx +++ b/frontend/src/screens/CreateProfileModal.tsx @@ -15,7 +15,8 @@ interface CreateProfileModalProps { type Phase = 'choice' | 'pairing' | 'paired' | 'local' | 'creating' | 'success'; export function CreateProfileModal({ open, onClose }: CreateProfileModalProps) { - const { state, createProfile, nip46PairStart, nip46Status, nip46Disconnect, refresh } = useApp(); + const { state, createProfile, nip46PairStart, nip46Status, nip46CancelPairing, refresh } = + useApp(); const [label, setLabel] = useState(''); const [phase, setPhase] = useState('choice'); const [error, setError] = useState(null); @@ -149,13 +150,16 @@ export function CreateProfileModal({ open, onClose }: CreateProfileModalProps) { // Leaving the QR view mid-pairing aborts the in-flight pairing; nothing // was persisted yet, so teardown is safe at any point (same as Signer - // Mode's "Cancel pairing"). + // Mode's "Cancel pairing"). Cancel (not disconnect): when pairing a + // second signer account parked the first one, cancelling must restore + // the parked session rather than revoke anything. const cancelPairing = async () => { setLivePairingUri(null); setPairingQr(null); setPhase('choice'); try { - await nip46Disconnect(); + await nip46CancelPairing(); + void refresh(); } catch { // Best-effort abort; a dead pairing attempt expires on its own. } diff --git a/frontend/src/screens/SignerModeScreen.tsx b/frontend/src/screens/SignerModeScreen.tsx index 2bdd00f..83dd1c7 100644 --- a/frontend/src/screens/SignerModeScreen.tsx +++ b/frontend/src/screens/SignerModeScreen.tsx @@ -17,6 +17,7 @@ export function SignerModeScreen() { nip46Connect, nip46PairStart, nip46Disconnect, + nip46CancelPairing, nip46Approve, embeddedSignerApprove, refresh, @@ -192,17 +193,19 @@ export function SignerModeScreen() { }; }, [pairingUri]); - // Abort an in-flight pairing (e.g. expired QR) — same teardown as a - // disconnect; nothing was persisted yet so it is safe at any point. + // Abort an in-flight pairing (e.g. expired QR): cancel the pairing + // attempt only. Never disconnect here — if pairing a second signer + // account parked the first one, a cancel must bring the parked session + // back instead of revoking it. const handlePairCancel = useCallback(async () => { setPairError(null); try { - const status = await nip46Disconnect(); + const status = await nip46CancelPairing(); setNip46StatusState(status); } catch (err) { setPairError(err instanceof Error ? err.message : String(err)); } - }, [nip46Disconnect]); + }, [nip46CancelPairing]); const handleNip46Disconnect = useCallback(async () => { setError(null); diff --git a/frontend/src/state/AppProvider.tsx b/frontend/src/state/AppProvider.tsx index 543620c..9aa5483 100644 --- a/frontend/src/state/AppProvider.tsx +++ b/frontend/src/state/AppProvider.tsx @@ -82,6 +82,7 @@ interface AppContextValue { nip46Connect: (uri: string, label: string) => Promise; nip46PairStart: (label: string) => Promise; nip46Disconnect: () => Promise; + nip46CancelPairing: () => Promise; nip46Status: () => Promise; nip46Approve: (id: string, approved: boolean, always?: boolean) => Promise; // Legacy NIP-46 bunker (deprecated) @@ -289,6 +290,7 @@ export function AppProvider({ children }: { children: ReactNode }) { [], ); const nip46Disconnect = useCallback(() => api.nip46Disconnect(), []); + const nip46CancelPairing = useCallback(() => api.nip46CancelPairing(), []); const nip46Status = useCallback(() => api.nip46Status(), []); const nip46PairStart = useCallback((label: string) => api.nip46PairStart(label), []); const nip46Approve = useCallback( @@ -385,6 +387,7 @@ export function AppProvider({ children }: { children: ReactNode }) { nip46Connect, nip46PairStart, nip46Disconnect, + nip46CancelPairing, nip46Status, nip46Approve, signerConnect, @@ -445,6 +448,7 @@ export function AppProvider({ children }: { children: ReactNode }) { nip46Connect, nip46PairStart, nip46Disconnect, + nip46CancelPairing, nip46Status, nip46Approve, signerConnect, diff --git a/frontend/src/test/CreateProfileModal.test.tsx b/frontend/src/test/CreateProfileModal.test.tsx index da456cb..a4d3123 100644 --- a/frontend/src/test/CreateProfileModal.test.tsx +++ b/frontend/src/test/CreateProfileModal.test.tsx @@ -137,7 +137,10 @@ describe('CreateProfileModal', () => { await screen.findByText(/Waiting for the signer to scan/i); await user.click(screen.getByRole('button', { name: 'Cancel pairing' })); - expect(backend.requests.some((r) => r.method === 'nip46_disconnect')).toBe(true); + // Cancel must be the NON-revoking cancel (parked sessions survive it), + // never the disconnect that revokes the stored connection. + expect(backend.requests.some((r) => r.method === 'nip46_cancel_pairing')).toBe(true); + expect(backend.requests.some((r) => r.method === 'nip46_disconnect')).toBe(false); expect( screen.getByRole('button', { name: /Sign in with a signer app \(Amber\)/ }), ).toBeInTheDocument(); diff --git a/frontend/src/test/fakeBackend.ts b/frontend/src/test/fakeBackend.ts index 47e6954..40b147c 100644 --- a/frontend/src/test/fakeBackend.ts +++ b/frontend/src/test/fakeBackend.ts @@ -226,6 +226,14 @@ export function createFakeBackend(initial?: AppState): FakeBackend { backend.setNip46(next); return next; } + case 'nip46_cancel_pairing': { + // Mirrors the real backend: aborts ONLY the pairing attempt and + // re-dials the parked session — a cancel must not clear a + // connected session, only the pairing URI. + const next = { ...backend.nip46, pairing_uri: undefined }; + backend.setNip46(next); + return next; + } case 'nip46_approve': return backend.nip46; diff --git a/src/ipc.rs b/src/ipc.rs index 5ed59d5..93f8330 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -173,6 +173,12 @@ pub enum Request { }, /// Disconnect from the NIP-46 signer. Nip46Disconnect, + /// Cancel an in-flight pairing (the GUI left the QR view): abort the + /// pairing attempt WITHOUT touching any parked/saved session, then try + /// to re-dial the active profile's saved session so parking for a + /// cancelled pairing is fully transparent. (Plain Nip46Disconnect would + /// revoke the currently stored connection — wrong for a cancel.) + Nip46CancelPairing, /// Get NIP-46 connection status. Nip46Status, /// Approve/reject a pending NIP-46 request. `always = true` additionally @@ -492,6 +498,13 @@ async fn run(app: &Arc>, request: Request) -> Result>, request: Request) -> Result>, request: Request) -> Result { + let Some(signer) = ensure_nip46_signer(app).await else { + return Err(AppError::config("NIP-46 signer not initialized")); + }; + signer.cancel_pairing().await?; + let status = signer.status().await; + Ok(json!(status)) + } Request::Nip46Status => { let Some(signer) = ensure_nip46_signer(app).await else { return Ok(json!({ "connected": false, "error": "Not initialized" })); @@ -654,6 +681,41 @@ async fn run(app: &Arc>, request: Request) -> Result { + { + let mut guard = app.lock().await; + profiles::set_active(&mut guard.vault, &npub)?; + if guard.signer_mode == SignerMode::Embedded { + if let Some(signer) = &guard.embedded_signer { + signer.set_active_profile(Some(npub.clone())).await; + } + } else if guard.signer_mode == SignerMode::Nip46Client { + if let Some(signer) = &guard.nip46_signer { + signer.set_active_profile(Some(npub.clone())).await; + } + } + guard.save_vault()?; + } + // Option A: follow the switch with the signer session. If the + // target profile has a restorable NIP-46 connection, the live + // session (if any, serving a different account) is parked and + // this profile's session is re-dialed — no fresh scan. If the + // target has no signer connection (local-key profile), the live + // session is left alone. + if app.lock().await.signer_mode == SignerMode::Nip46Client { + if let Some(signer) = ensure_nip46_signer(app).await { + if let Err(e) = signer.switch_to_profile(&npub).await { + eprintln!("[NIP46] profile switch session change failed: {e}"); + } + } + } + let guard = app.lock().await; + Ok(json!(guard.state_view())) + } + // Vault state requests (require lock) other => { let mut guard = app.lock().await; @@ -711,21 +773,8 @@ async fn run_with_app(app: &mut App, request: Request) -> Result { - profiles::set_active(&mut app.vault, &npub)?; - if app.signer_mode == SignerMode::Embedded { - if let Some(signer) = &app.embedded_signer { - signer.set_active_profile(Some(npub)).await; - } - } else if app.signer_mode == SignerMode::Nip46Client { - if let Some(signer) = &app.nip46_signer { - signer.set_active_profile(Some(npub)).await; - } - } - app.save_vault()?; - Ok(json!(app.state_view())) - } - + // NOTE: SelectProfile is handled in `run` (above), not here — it + // must drop the App guard before switching the signer session. Request::PublishProfileMetadata { npub } => { // Route through the profile's Signing source, exactly like // PublishNote: an embedded profile signs locally, a paired diff --git a/src/signer/nip46_client.rs b/src/signer/nip46_client.rs index 0d6ab3d..1248714 100644 --- a/src/signer/nip46_client.rs +++ b/src/signer/nip46_client.rs @@ -488,6 +488,148 @@ impl Nip46ClientSigner { self.disconnect().await } + /// Park the live session: stop the wire task and clear in-memory state, + /// but keep the vault row, pairing secret, and persisted client key + /// INTACT, so the session can be re-dialed later with no fresh scan. + /// + /// Unlike [`Self::disconnect`] this does NOT revoke: switching signer + /// accounts (pair a second Amber account, or switch back to a previously + /// paired one) must leave the parked account restorable. The vault row + /// is what `reactivate_saved_sessions` dials from, so a parked session + /// is exactly a saved session. + pub async fn park_live_session(&self) { + let mut inner = self.inner.lock().await; + if let Some(task) = inner.task.take() { + task.abort(); + } + if let Some(pairing) = inner.pairing.take() { + pairing.task.abort(); + } + if let Some(client) = inner.client.take() { + let _ = client.disconnect().await; + } + inner.phase = Nip46Phase::Stopped; + inner.connection = None; + inner.conversation_key = None; + inner.keys = None; + inner.identity = None; + inner.expected_identity = None; + inner.active_npub = None; + inner.pending.clear(); + // Wake any callers awaiting a remote response; their waiters turn + // into `NotConnected` rather than hanging until the request timeout. + for (_, waiter) in inner.remote_pending.drain() { + let _ = waiter.sender.send(Err( + "Switched signer accounts before the signer responded.".to_string(), + )); + } + } + + /// Whether a session or pairing is currently live. + pub async fn has_live_session(&self) -> bool { + let inner = self.inner.lock().await; + inner.task.is_some() || inner.pairing.is_some() + } + + /// Cancel an in-flight pairing attempt: abort ONLY the pairing task and + /// its session state, then try to re-dial the active profile's saved + /// session. Used by the GUI when the user leaves the QR view after + /// pairing-for-a-second-account parked the first one: the cancel must + /// not revoke anything, and the parked session should come back so the + /// park is invisible. + pub async fn cancel_pairing(&self) -> Result<(), AppError> { + { + let mut inner = self.inner.lock().await; + if let Some(pairing) = inner.pairing.take() { + pairing.task.abort(); + } + // A pairing that was cancelled before anyone scanned never + // reached the vault; the only live slot it held is now free. + // If a *connected* session exists (task, not pairing), leave it + // alone — cancelling pairing must not kill a working session. + if inner.task.is_some() { + return Ok(()); + } + inner.phase = Nip46Phase::Stopped; + inner.connection = None; + inner.conversation_key = None; + inner.keys = None; + inner.identity = None; + inner.expected_identity = None; + inner.active_npub = None; + inner.pending.clear(); + } + // The park-then-pair flow (Option A) may have left the previous + // account parked: re-dial it (reactivate prefers the active + // profile's row) so the cancelled pairing changes nothing. + self.reactivate_saved_sessions().await?; + Ok(()) + } + + /// Follow a profile switch with the signer session (Option A: one live + /// session, many saved ones). + /// + /// - The live session already serves `npub`: keep it, do nothing. + /// - `npub` has no live saved NIP-46 connection (local-key profile, or + /// never paired): leave the live session alone — signing for `npub` + /// routes through its own source and killing a working Amber session + /// to look at a local profile would be a regression. + /// - `npub` has a restorable saved connection: park the live session + /// (restorable, not revoked) and re-dial `npub`'s row. + /// + /// Returns `true` when a re-dial was started. The re-dial resolves + /// asynchronously through the same handshake as restore, including the + /// `expected_identity` cross-account guard. + pub async fn switch_to_profile(&self, npub: &str) -> Result { + // Already serving the target identity? Nothing to do. + { + let inner = self.inner.lock().await; + if let Some(identity) = &inner.identity { + if identity.to_bech32().ok().as_deref() == Some(npub) { + return Ok(false); + } + } + } + + // Does the target profile have a live, restorable saved connection? + let restorable = { + let app = self.app.lock().await; + let vault_key = app.vault_key().copied(); + let now = crate::vault::unix_timestamp().unwrap_or(0); + app.vault.nip46_connections.iter().any(|c| { + c.profile_npub.as_deref() == Some(npub) + && c.revoked_at.is_none() + && c.expires_at.map(|t| t > now).unwrap_or(true) + && crate::vault::resolve_connection_client_key( + &app.vault, + vault_key.as_ref(), + &crate::signer::VaultRef::from_connection(c), + ) + .ok() + .flatten() + .is_some() + }) + }; + if !restorable { + return Ok(false); + } + + // Make sure the vault agrees on the target before re-dialing (the + // IPC path already did this; idempotent here, and it makes the + // reactivate preference order correct regardless of caller). + { + let mut app = self.app.lock().await; + if app.vault.active_profile.as_deref() != Some(npub) { + crate::profiles::set_active(&mut app.vault, npub)?; + app.save_vault()?; + } + } + + self.park_live_session().await; + let dialed = self.reactivate_saved_sessions().await?; + Ok(dialed > 0) + } + /// Re-dial the saved signer sessions after startup/unlock, no scan. /// /// Amber remembers our *client pubkey* as the identity of an approved diff --git a/tests/nip46_e2e.rs b/tests/nip46_e2e.rs index 86b281b..0737396 100644 --- a/tests/nip46_e2e.rs +++ b/tests/nip46_e2e.rs @@ -1324,3 +1324,248 @@ async fn nip46_session_restore_redials_and_refuses_wrong_identity() { tokio::time::sleep(Duration::from_millis(100)).await; } } + +// --------------------------------------------------------------------------- +// Option A: many saved sessions, one live. Pairing/connecting a second +// signer account PARKS the live session (restorable, never revoked), and +// switching a profile back to a parked account re-dials it with no scan. +// --------------------------------------------------------------------------- + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +#[allow(clippy::await_holding_lock)] +async fn nip46_second_account_parks_first_and_switch_restores_it() { + let _vault_guard = VAULT_ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + let app = { + let tmp = std::env::temp_dir().join(format!( + "keynectr-e2e-switch-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + let app_dir = tmp.join("keynectr"); + std::fs::create_dir_all(&app_dir).unwrap(); + std::fs::write( + app_dir.join("profiles_vault.json"), + serde_json::to_string(&Vault::empty()).unwrap(), + ) + .unwrap(); + std::env::set_var("XDG_DATA_HOME", &tmp); + let app = std::sync::Arc::new(Mutex::new(App::load().expect("load app"))); + assert!( + app.try_lock().unwrap().vault.profiles.is_empty(), + "e2e vault isolation failed for switch test" + ); + app + }; + + let relay_url = start_relay().await; + let comms_a = Keys::generate(); + let identity_a = Keys::generate(); + let comms_b = Keys::generate(); + let identity_b = Keys::generate(); + // Two fake Ambers on one relay: each only answers traffic it can + // NIP-44-decrypt with its own comms key, so they never cross-talk. + tokio::spawn(run_fake_amber( + relay_url.clone(), + comms_a.clone(), + identity_a.clone(), + Duration::from_millis(30), + )); + tokio::spawn(run_fake_amber( + relay_url.clone(), + comms_b.clone(), + identity_b.clone(), + Duration::from_millis(30), + )); + + let signer = Nip46ClientSigner::new(app.clone()); + + let wait_connected = |signer: Nip46ClientSigner| async move { + let deadline = tokio::time::Instant::now() + Duration::from_secs(15); + loop { + let st = signer.status().await; + if let Some(err) = &st.error { + panic!("session failed: {err}"); + } + if st.connected { + return; + } + assert!( + tokio::time::Instant::now() < deadline, + "session never connected: {:?}", + signer.status().await + ); + tokio::time::sleep(Duration::from_millis(100)).await; + } + }; + + // --- 1. Account A pairs (the flow the GUI runs). + signer + .connect( + &format!( + "bunker://{}?relay={}", + comms_a.public_key().to_hex(), + relay_url + ), + "amber A".to_string(), + ) + .await + .expect("connect account A"); + wait_connected(signer.clone()).await; + let npub_a = SignerTrait::get_public_key(&signer) + .await + .expect("identity A") + .to_bech32() + .unwrap(); + + // --- 2. Account B pairs while A is live. The IPC dispatcher parks the + // live session first (the exact sequence the dispatcher now runs). + assert!(signer.has_live_session().await); + signer.park_live_session().await; + assert!( + !signer.has_live_session().await, + "park must clear the live slot" + ); + signer + .connect( + &format!( + "bunker://{}?relay={}", + comms_b.public_key().to_hex(), + relay_url + ), + "amber B".to_string(), + ) + .await + .expect("connect account B while A is parked"); + wait_connected(signer.clone()).await; + let npub_b = SignerTrait::get_public_key(&signer) + .await + .expect("identity B") + .to_bech32() + .unwrap(); + assert_ne!(npub_a, npub_b, "two accounts, two identities"); + + // B is fully usable: sign through it. + let unsigned = UnsignedEvent::new( + identity_b.public_key(), + Timestamp::now(), + Kind::TextNote, + vec![], + "signed by B".to_string(), + ); + let signed = SignerTrait::sign_event(&signer, unsigned.clone()) + .await + .expect("sign through B"); + assert!(signed.verify_signature()); + + // Parking must NOT have revoked A: its row stays live with its client + // key resolvable — that is what makes it restorable. + let ref_a = + keynectr::signer::VaultRef::new(Some(npub_a.clone()), comms_a.public_key().to_hex()); + { + let g = app.lock().await; + let row = g + .vault + .nip46_connections + .iter() + .find(|c| c.profile_npub.as_deref() == Some(npub_a.as_str())) + .expect("A's connection row survives B's pairing"); + assert!( + row.revoked_at.is_none(), + "parked session must not be revoked" + ); + assert!( + keynectr::vault::resolve_connection_client_key(&g.vault, None, &ref_a) + .expect("resolve") + .is_some(), + "parked session must keep its client key" + ); + } + + // --- 3. Switch back to A: park B, re-dial A — no fresh pairing. + let dialed = signer + .switch_to_profile(&npub_a) + .await + .expect("switch to A"); + assert!(dialed, "A has a restorable session, switch must re-dial it"); + wait_connected(signer.clone()).await; + let back = SignerTrait::get_public_key(&signer) + .await + .expect("identity after switch"); + assert_eq!( + back.to_bech32().unwrap(), + npub_a, + "switched session must answer as A" + ); + let unsigned_a = UnsignedEvent::new( + identity_a.public_key(), + Timestamp::now(), + Kind::TextNote, + vec![], + "signed by A again".to_string(), + ); + let signed_a = SignerTrait::sign_event(&signer, unsigned_a.clone()) + .await + .expect("sign through A after switch"); + assert!(signed_a.verify_signature()); + assert_eq!(signed_a.content, "signed by A again"); + + // Switching to A again is a no-op (already serving A): no second dial. + assert!( + !signer + .switch_to_profile(&npub_a) + .await + .expect("noop switch"), + "switch to the identity already live must not re-dial" + ); + + // --- 4. A profile with NO signer connection must leave B... (here A) + // alone: local-key profiles route signing through their own source. + let local = Keys::generate(); + let npub_local = local.public_key().to_bech32().unwrap(); + { + let mut g = app.lock().await; + g.vault.profiles.push(keynectr::vault::StoredProfile { + label: "local".to_string(), + public_key: npub_local.clone(), + secret_key: local + .secret_key() + .to_secret_bytes() + .iter() + .map(|b| format!("{b:02x}")) + .collect(), + created_at: 0, + picture: None, + nip05: None, + signer_mode: keynectr::vault::SignerMode::Embedded, + }); + g.save_vault().unwrap(); + } + assert!( + !signer + .switch_to_profile(&npub_local) + .await + .expect("switch to local"), + "local-key profile must not touch the live signer session" + ); + assert!( + signer.status().await.connected, + "live A session survives a switch to a local profile" + ); + let still_a = SignerTrait::get_public_key(&signer).await.expect("still A"); + assert_eq!(still_a.to_bech32().unwrap(), npub_a); + + // And switching back to B works too — B was parked, never revoked. + let dialed_b = signer + .switch_to_profile(&npub_b) + .await + .expect("switch back to B"); + assert!(dialed_b, "B was parked, must be restorable"); + wait_connected(signer.clone()).await; + let b_again = SignerTrait::get_public_key(&signer).await.expect("B again"); + assert_eq!(b_again.to_bech32().unwrap(), npub_b); + + signer.disconnect().await.ok(); +} From 98593cb1704651858a5eae685a64f29141a7f501 Mon Sep 17 00:00:00 2001 From: Avi Date: Sun, 27 Sep 2026 21:02:21 -0500 Subject: [PATCH 04/17] docs(checkpoint): multi-account signer switching (park/switch/cancel) at c89b31a (2026-09-27) --- CHECKPOINT-encryption.md | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/CHECKPOINT-encryption.md b/CHECKPOINT-encryption.md index e88cb10..9f3d5d9 100644 --- a/CHECKPOINT-encryption.md +++ b/CHECKPOINT-encryption.md @@ -1,3 +1,62 @@ +# Checkpoint — multi-account signer switching (Option A) (2026-09-27 eve) + +## Where things are +- Project: `/home/avi/Projects/Keynctr`, branch `master` @ **`c89b31a`** + ("feat(nip46): pair a second signer account — park the live session, + switch re-dials it"). Previous: `1b4655c` (checkpoint), `332ab64`. +- Working tree: clean for tracked files. Untracked intentionally NOT + committed: `COSMIC_THEME.md`, `KeynectrAppIconPossibility02.jpeg`, + `deferred/`. + +## What was completed (user-facing) +1. **You can now add a second Amber account.** Pairing a new signer while + one is connected no longer says "Already connected — disconnect + first": the current session is PARKED (kept restorable, never revoked) + and the new account pairs. +2. **Switching profiles switches signer accounts.** Click a profile in + Profiles: if it has a saved signer session, the live one is parked and + that profile's session is re-dialed automatically (no scan, identity + guard still enforced). Local-key profiles leave the signer alone. +3. **Cancel on the QR is safe.** Leaving the QR view cancels only the + pairing attempt and brings the parked session back (new + `nip46_cancel_pairing` IPC; the old path revoked). +- One session is live at a time (Amber signs one active account anyway); + all others stay saved and switchable. + +## Commits added +- `c89b31a` feat(nip46): pair a second signer account — park the live session, switch re-dials it + +## Verification (all green at c89b31a) +- Rust: `cargo test` 216 unit + 6 e2e (NEW: two fake Ambers on one relay — + B's pairing parks A unrevoked with client key intact; switch back + re-dials A and signs; no-op switch; local profile untouched; B + restorable). `cargo clippy --all-targets` 0 warnings; `cargo fmt --check` + clean; `cargo build --release` rebuilt (binary mtime Sep 27 21:00). +- Frontend: `npm test` 125 passed; `typecheck`, `lint`, `format:check` + clean; `npm run build` + `electron:build` green. + +## Resume / reproduce +- GUI: relaunch the app (or restart the backend) to pick up the new + release binary. Profiles -> click another paired profile -> log shows + `restoring session` + `identity check on restored session: PASS`. +- Add account B: Create Profile -> Sign in with Amber -> switch to + account B IN AMBER -> scan. Account A stays restorable. +- e2e: `cargo test --test nip46_e2e` (6 tests, loopback relay only). + +## Outstanding +- LIVE two-account eyeball by the user (pair account B from a second + Amber profile, switch back and forth) — e2e proves the mechanics, a + real-device pass confirms it end-to-end. +- Live "Check for updates" failure is an ENVIRONMENT issue, not a bug: + the updater shells out to `npm outdated`/`cargo update` in the source + tree; the spawned backend's PATH lacks npm/cargo (Electron-launched + process), so it errors. Fix options: bake a login-shell PATH into the + updater or surface a clearer message. Not started. +- Publish kind-0 to primal/damus (one Amber approval); Step 4 + permissions UI; KDF upgrade (Step 5); rename pass (Step 7). + +--- + # Checkpoint — forensics log cleaned + live publish confirmed (2026-09-26) ## Where things are From adbc7c2d7587a27d98ee7d99ef15f2c139151b4a Mon Sep 17 00:00:00 2001 From: Avi Date: Sun, 27 Sep 2026 22:37:54 -0500 Subject: [PATCH 05/17] =?UTF-8?q?feat(signer):=20permissions=20UI=20?= =?UTF-8?q?=E2=80=94=20declared=20grants=20surfaced,=20always-allow=20kind?= =?UTF-8?q?-scoped?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 4 groundwork, the enforcement half that was invisible or too broad: - Nip46Status now carries the connection's declared perms= grant list and its expiry; Signer Mode shows a Permissions panel on a live session (explicit grant rows, or a plain statement that the signer app approves each request when no list was declared). - 'Always allow' grants are kind-scoped: a sign_event grant records the kind of the request the user actually approved and never covers other kinds. Legacy kind-less grants keep their all-kinds meaning so existing vaults keep working. Enforced in both bunker.rs and nip46_client.rs. - Grants list on the Signer screen renders human labels with kind scope. Tests: vault kind-scoping unit tests, frontend permission-label unit tests + two SignerModeScreen tests (declared list, signer-side note). --- frontend/src/lib/permissions.ts | 52 ++++++++++++ frontend/src/lib/types.ts | 20 +++++ frontend/src/screens/SignerModeScreen.tsx | 35 ++++++++ frontend/src/screens/SignerScreen.tsx | 3 +- frontend/src/styles.css | 20 +++++ frontend/src/test/SignerModeScreen.test.tsx | 36 ++++++++ frontend/src/test/permissions.test.ts | 53 ++++++++++++ src/bunker.rs | 20 ++++- src/signer/nip46_client.rs | 41 ++++++---- src/signer/types.rs | 10 +++ src/vault.rs | 91 +++++++++++++++++---- 11 files changed, 350 insertions(+), 31 deletions(-) create mode 100644 frontend/src/lib/permissions.ts create mode 100644 frontend/src/test/permissions.test.ts diff --git a/frontend/src/lib/permissions.ts b/frontend/src/lib/permissions.ts new file mode 100644 index 0000000..aed1d04 --- /dev/null +++ b/frontend/src/lib/permissions.ts @@ -0,0 +1,52 @@ +import type { Nip46Permissions, SignerGrant } from './types'; + +/** Human label for a NIP-46 method name. */ +export function methodLabel(method: string): string { + switch (method) { + case 'sign_event': + return 'Sign events'; + case 'nip44_encrypt': + return 'Encrypt messages (NIP-44)'; + case 'nip44_decrypt': + return 'Decrypt messages (NIP-44)'; + case 'get_public_key': + return 'Read your public key'; + case 'get_relays': + return 'Read your relay list'; + default: + return method; + } +} + +/** One-line description of a permission grant, e.g. + * "Sign events (kinds 1, 30023)" or "Sign events (all kinds)". */ +export function permissionLabel(method: string, allowedKinds?: number[]): string { + const base = methodLabel(method); + if (method === 'sign_event') { + if (!allowedKinds || allowedKinds.length === 0) return `${base} — all kinds`; + return `${base} — kinds ${allowedKinds.join(', ')}`; + } + return base; +} + +/** Grant rows for the "always allow" list. */ +export function grantLabel(grant: SignerGrant): string { + return permissionLabel(grant.method, grant.allowed_kinds); +} + +/** Rows for the declared per-connection permission set. An absent set means + * there is no local grant list — the signer app approves each request. */ +export function declaredPermissionRows(permissions?: Nip46Permissions): string[] | null { + if (!permissions) return null; + const granted = permissions.granted ?? []; + if (granted.length === 0) return []; + return granted.map((p) => permissionLabel(p.method, p.allowed_kinds)); +} + +/** Format a connection expiry for display. */ +export function formatExpiry(expiresAt?: number): string | null { + if (!expiresAt) return null; + const date = new Date(expiresAt * 1000); + if (Number.isNaN(date.getTime())) return null; + return date.toLocaleString(); +} diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index d0f98ab..a255dd9 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -29,6 +29,19 @@ export interface PendingApproval { details?: ApprovalDetails; } +/** A single granted NIP-46 permission (mirrors the Rust Nip46Permission). */ +export interface Nip46Permission { + /** The NIP-46 method this covers, e.g. `sign_event`. */ + method: string; + /** Event-kind restrictions for `sign_event`; empty = all kinds. */ + allowed_kinds?: number[]; +} + +/** Declared per-connection permission set (from a `perms=` connect URI). */ +export interface Nip46Permissions { + granted?: Nip46Permission[]; +} + /** A standing "always allow" grant: one app may use one method without a * prompt. Created by choosing "Always allow" on an approval; revoked from * the Signer screen. */ @@ -37,6 +50,8 @@ export interface SignerGrant { app_pubkey: string; /** NIP-46 method that runs without prompting (e.g. "sign_event"). */ method: string; + /** Event kinds covered for `sign_event`; empty = all kinds (legacy). */ + allowed_kinds?: number[]; } /** Non-secret snapshot of the NIP-46 remote signer for display. */ @@ -75,6 +90,11 @@ export interface Nip46SignerStatus { pending_approvals: PendingApproval[]; /** nostrconnect:// pairing token while a QR pairing is in flight. */ pairing_uri?: string; + /** Declared per-connection permissions, when the connect URI carried a + * `perms=` grant list. Absent = the signer app enforces via its prompts. */ + permissions?: Nip46Permissions; + /** Unix timestamp when the connection expires, if it has a deadline. */ + expires_at?: number; } /** Union of all signer statuses. */ diff --git a/frontend/src/screens/SignerModeScreen.tsx b/frontend/src/screens/SignerModeScreen.tsx index 83dd1c7..51a46c8 100644 --- a/frontend/src/screens/SignerModeScreen.tsx +++ b/frontend/src/screens/SignerModeScreen.tsx @@ -5,6 +5,7 @@ import { Badge } from '../components/Badge'; import { Button } from '../components/Button'; import { ErrorText } from '../components/ErrorText'; import { Icon } from '../components/Icon'; +import { declaredPermissionRows, formatExpiry } from '../lib/permissions'; import type { SignerMode, EmbeddedSignerStatus, Nip46SignerStatus } from '../lib/types'; import { useApp } from '../state/AppProvider'; @@ -528,6 +529,40 @@ export function SignerModeScreen() { via {nip46StatusState.connected_relays?.length ?? 0} of{' '} {nip46StatusState.relays?.length ?? 0} relays

+
+

Permissions

+ {(() => { + const rows = declaredPermissionRows(nip46StatusState.permissions); + const expiry = formatExpiry(nip46StatusState.expires_at); + return ( + <> + {rows === null ? ( +

+ This signer approves every request on your phone — Keynctr holds no + standing permission list for this connection. +

+ ) : rows.length === 0 ? ( +

+ No operations were granted by the connect request. +

+ ) : ( +
    + {rows.map((row) => ( +
  • + {row} +
  • + ))} +
+ )} + {expiry && ( +

+ This connection expires {expiry}. +

+ )} + + ); + })()} +
{nip46StatusState.error && ( {nip46StatusState.error} diff --git a/frontend/src/screens/SignerScreen.tsx b/frontend/src/screens/SignerScreen.tsx index b55d897..94a1fe3 100644 --- a/frontend/src/screens/SignerScreen.tsx +++ b/frontend/src/screens/SignerScreen.tsx @@ -5,6 +5,7 @@ import { Button } from '../components/Button'; import { ErrorText } from '../components/ErrorText'; import { Icon } from '../components/Icon'; import { shortHexId } from '../lib/format'; +import { grantLabel } from '../lib/permissions'; import type { SignerGrant, SignerStatus } from '../lib/types'; import { useApp } from '../state/AppProvider'; @@ -252,7 +253,7 @@ export function SignerScreen() { {grants.map((grant) => (
- {grant.method} + {grantLabel(grant)}

for {shortHexId(grant.app_pubkey)}

diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 3d1f373..5c3c1d6 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -2242,6 +2242,26 @@ select { gap: 12px; } +.signer-permissions { + width: 100%; + padding: 10px 12px; + background: var(--surface-2); + border: 1px solid var(--border); + border-radius: var(--radius-sm); +} + +.signer-permissions h3 { + margin: 0 0 6px; + font-size: 13px; +} + +.signer-permission-list { + margin: 0; + padding-left: 18px; + font-size: 12px; + line-height: 1.7; +} + /* ------------------------------------------------------------------------- Motion system Purposeful motion for feedback, state, and continuity. diff --git a/frontend/src/test/SignerModeScreen.test.tsx b/frontend/src/test/SignerModeScreen.test.tsx index a4a94f4..f62b51d 100644 --- a/frontend/src/test/SignerModeScreen.test.tsx +++ b/frontend/src/test/SignerModeScreen.test.tsx @@ -69,4 +69,40 @@ describe('SignerModeScreen handshake states', () => { expect(backend.requests.some((r) => r.method === 'nip46_status')).toBe(true), ); }); + + it('shows the declared permission list on a connected session', async () => { + const backend = installNip46Backend(); + backend.setNip46({ + type: 'nip46', + connected: true, + signer_pubkey: 'aabbccddeeff0011', + relays: ['wss://relay.test'], + connected_relays: ['wss://relay.test'], + pending_approvals: [], + permissions: { + granted: [{ method: 'sign_event', allowed_kinds: [1, 30023] }, { method: 'nip44_encrypt' }], + }, + }); + renderWithApp(); + + expect(await screen.findByText('Permissions')).toBeInTheDocument(); + expect(screen.getByText('Sign events — kinds 1, 30023')).toBeInTheDocument(); + expect(screen.getByText('Encrypt messages (NIP-44)')).toBeInTheDocument(); + }); + + it('explains signer-side enforcement when no grant list was declared', async () => { + const backend = installNip46Backend(); + backend.setNip46({ + type: 'nip46', + connected: true, + signer_pubkey: 'aabbccddeeff0011', + relays: ['wss://relay.test'], + connected_relays: ['wss://relay.test'], + pending_approvals: [], + }); + renderWithApp(); + + expect(await screen.findByText('Permissions')).toBeInTheDocument(); + expect(await screen.findByText(/approves every request on your phone/i)).toBeInTheDocument(); + }); }); diff --git a/frontend/src/test/permissions.test.ts b/frontend/src/test/permissions.test.ts new file mode 100644 index 0000000..d5d6f08 --- /dev/null +++ b/frontend/src/test/permissions.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from 'vitest'; +import { + declaredPermissionRows, + formatExpiry, + grantLabel, + permissionLabel, +} from '../lib/permissions'; + +describe('permission labels', () => { + it('labels a kind-scoped sign_event grant', () => { + expect(permissionLabel('sign_event', [1, 30023])).toBe('Sign events — kinds 1, 30023'); + }); + + it('labels an all-kinds sign_event grant', () => { + expect(permissionLabel('sign_event', [])).toBe('Sign events — all kinds'); + expect(permissionLabel('sign_event')).toBe('Sign events — all kinds'); + }); + + it('labels non-signing methods without kind noise', () => { + expect(permissionLabel('nip44_decrypt')).toBe('Decrypt messages (NIP-44)'); + }); + + it('renders a grant row through grantLabel', () => { + expect(grantLabel({ app_pubkey: 'aa', method: 'sign_event', allowed_kinds: [1] })).toBe( + 'Sign events — kinds 1', + ); + }); +}); + +describe('declared permission rows', () => { + it('returns null when the connection declared no grant list', () => { + expect(declaredPermissionRows(undefined)).toBeNull(); + expect(declaredPermissionRows({})).toEqual([]); + }); + + it('renders each granted method', () => { + expect( + declaredPermissionRows({ + granted: [{ method: 'sign_event', allowed_kinds: [1] }, { method: 'nip44_encrypt' }], + }), + ).toEqual(['Sign events — kinds 1', 'Encrypt messages (NIP-44)']); + }); +}); + +describe('formatExpiry', () => { + it('formats a unix timestamp', () => { + expect(formatExpiry(1760000000)).toBeTruthy(); + }); + + it('returns null when there is no deadline', () => { + expect(formatExpiry(undefined)).toBeNull(); + }); +}); diff --git a/src/bunker.rs b/src/bunker.rs index 3f2b47b..a908b25 100644 --- a/src/bunker.rs +++ b/src/bunker.rs @@ -402,6 +402,23 @@ struct RawRequest { params: Vec, } +/// The event kind a gated request operates on, when it has one. +/// `sign_event` carries the unsigned event JSON in `params[0]`; other +/// gated methods (encrypt/decrypt) have no kind dimension, and an +/// unparseable payload returns `None` so grant checks fail closed toward +/// prompting. +fn request_kind(request: &RawRequest) -> Option { + if request.method != "sign_event" { + return None; + } + request + .params + .first() + .and_then(|json| serde_json::from_str::(json).ok()) + .and_then(|v| v.get("kind").and_then(|k| k.as_u64())) + .map(|k| k as u16) +} + /// `{"id":..,"result":,"error":null}` fn response_ok(id: &str, result: String) -> String { json!({ "id": id, "result": result, "error": null }).to_string() @@ -754,11 +771,12 @@ async fn run_sign_task(signer: Signer, app: Arc>, uri: C // run — unless the user granted this app standing "always allow" // permission for that method. Everything else is answered immediately. let response = if requires_approval(&request.method) { + let kind = request_kind(&request); let granted = { let guard = app.lock().await; guard .vault - .has_signer_grant(&uri.peer.to_hex(), &request.method) + .has_signer_grant(&uri.peer.to_hex(), &request.method, kind) }; if granted { approved_response(&keys, &request) diff --git a/src/signer/nip46_client.rs b/src/signer/nip46_client.rs index 1248714..cc85cb8 100644 --- a/src/signer/nip46_client.rs +++ b/src/signer/nip46_client.rs @@ -1382,6 +1382,8 @@ impl Nip46ClientSigner { } else { None }, + permissions: connection.as_ref().and_then(|c| c.permissions.clone()), + expires_at: connection.as_ref().and_then(|c| c.expires_at), } } @@ -1431,8 +1433,17 @@ impl Nip46ClientSigner { (entry, peer) }; if approved && always && !peer_hex.is_empty() { + // A "sign_event" always-allow covers only the kinds of the + // request the user actually saw — never other kinds. Other + // gated methods have no kind dimension. + let kinds: Vec = if entry.method == "sign_event" { + entry.details.event_kind.into_iter().collect() + } else { + Vec::new() + }; let mut app = self.app.lock().await; - app.vault.grant_signer_method(&peer_hex, &entry.method)?; + app.vault + .grant_signer_method(&peer_hex, &entry.method, &kinds)?; app.save_vault()?; } let _ = entry.sender.send(if approved { @@ -1906,16 +1917,14 @@ impl Nip46ClientSigner { } // Check method permissions + let event_kind = request + .params + .first() + .and_then(|json| serde_json::from_str::(json).ok()) + .and_then(|v| v.get("kind").and_then(|k| k.as_u64())) + .map(|k| k as u16); let allowed = match request.method.as_str() { - "sign_event" => { - let kind = request - .params - .first() - .and_then(|json| serde_json::from_str::(json).ok()) - .and_then(|v| v.get("kind").and_then(|k| k.as_u64())) - .unwrap_or(0) as u16; - self.can_sign_event(kind).await - } + "sign_event" => self.can_sign_event(event_kind.unwrap_or(0)).await, "nip44_encrypt" => self.can_encrypt().await, "nip44_decrypt" => self.can_decrypt().await, _ => false, @@ -1929,9 +1938,10 @@ impl Nip46ClientSigner { )); } - // Standing grant ("always allow") for this peer + method: skip the - // prompt and run. Grants are per (peer pubkey, method) and revocable - // from the Signer screen. + // Standing grant ("always allow") for this peer + method + kind: + // skip the prompt and run. Grants are per (peer pubkey, method, + // kind-set) and revocable from the Signer screen — a kind-1 grant + // never covers a kind-3 request. { let peer_hex = self .inner @@ -1943,7 +1953,10 @@ impl Nip46ClientSigner { .unwrap_or_default(); if !peer_hex.is_empty() { let app = self.app.lock().await; - if app.vault.has_signer_grant(&peer_hex, &request.method) { + if app + .vault + .has_signer_grant(&peer_hex, &request.method, event_kind) + { drop(app); self.inner.lock().await.phase = Nip46Phase::Connected; return self.approved_response(keys, request); diff --git a/src/signer/types.rs b/src/signer/types.rs index 1334e85..876ce83 100644 --- a/src/signer/types.rs +++ b/src/signer/types.rs @@ -98,6 +98,16 @@ pub struct Nip46Status { /// `None` once paired or when not pairing. #[serde(default, skip_serializing_if = "Option::is_none")] pub pairing_uri: Option, + /// The declared per-connection permissions for the live session, when + /// the connect URI carried a `perms=` grant list. `None` means no local + /// grant list — enforcement is the signer app's own approval prompts. + /// Surfaced so the UI can show what the connection was granted. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub permissions: Option, + /// When the live connection expires (unix timestamp), if it has a + /// deadline. Expired connections are refused at request time. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub expires_at: Option, } /// A pending approval request from the signer. diff --git a/src/vault.rs b/src/vault.rs index 0e4d907..0791f26 100644 --- a/src/vault.rs +++ b/src/vault.rs @@ -149,6 +149,16 @@ pub struct SignerGrant { /// The gated NIP-46 method covered: `sign_event`, `nip44_encrypt`, /// or `nip44_decrypt`. pub method: String, + /// Event kinds covered when `method` is `sign_event`. + /// + /// A grant is created "always allow" against ONE concrete request, so a + /// new `sign_event` grant carries exactly the kinds of that request. + /// A non-empty list restricts the grant to those kinds; an empty list + /// means all kinds — possible only on legacy grants (written before + /// grants were kind-scoped), never created anew. Grants for other + /// methods always leave this empty. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub allowed_kinds: Vec, /// Unix timestamp of when the user granted it. pub created_at: u64, } @@ -206,22 +216,44 @@ impl Vault { } /// Whether `app_pubkey` may run gated `method` without a prompt. - pub fn has_signer_grant(&self, app_pubkey: &str, method: &str) -> bool { - self.signer_grants - .iter() - .any(|g| g.app_pubkey == app_pubkey && g.method == method) + /// + /// For `sign_event` the grant is kind-scoped: a grant recorded for + /// specific kinds does NOT cover other kinds — an uncovered kind falls + /// back to the approval prompt. `None` for `event_kind` means the + /// request has no kind dimension (encrypt/decrypt) or the kind could + /// not be parsed; a kind-restricted grant never answers `None`, so + /// unparseable kinds fail closed toward prompting. + pub fn has_signer_grant( + &self, + app_pubkey: &str, + method: &str, + event_kind: Option, + ) -> bool { + self.signer_grants.iter().any(|g| { + g.app_pubkey == app_pubkey + && g.method == method + && (g.allowed_kinds.is_empty() + || event_kind.is_some_and(|k| g.allowed_kinds.contains(&k))) + }) } /// Record an "always allow" grant (idempotent). + /// + /// `allowed_kinds` scopes a `sign_event` grant to the kinds of the + /// request the user actually approved. Pass an empty slice for + /// non-signing methods and for kinds the user did not see — a new + /// grant never silently covers more than the request behind it. pub fn grant_signer_method( &mut self, app_pubkey: &str, method: &str, + allowed_kinds: &[u16], ) -> Result<(), crate::errors::AppError> { - if !self.has_signer_grant(app_pubkey, method) { + if !self.has_signer_grant(app_pubkey, method, allowed_kinds.first().copied()) { self.signer_grants.push(SignerGrant { app_pubkey: app_pubkey.to_string(), method: method.to_string(), + allowed_kinds: allowed_kinds.to_vec(), created_at: crate::vault::unix_timestamp()?, }); } @@ -796,24 +828,53 @@ mod tests { #[test] fn signer_grants_roundtrip_and_revoke() { let mut vault = Vault::empty(); - assert!(!vault.has_signer_grant("aa", "sign_event")); + assert!(!vault.has_signer_grant("aa", "sign_event", Some(1))); - vault.grant_signer_method("aa", "sign_event").unwrap(); - vault.grant_signer_method("aa", "sign_event").unwrap(); // idempotent - vault.grant_signer_method("bb", "sign_event").unwrap(); + vault.grant_signer_method("aa", "sign_event", &[1]).unwrap(); + vault.grant_signer_method("aa", "sign_event", &[1]).unwrap(); // idempotent + vault.grant_signer_method("bb", "sign_event", &[1]).unwrap(); assert_eq!(vault.signer_grants.len(), 2); - assert!(vault.has_signer_grant("aa", "sign_event")); - assert!(!vault.has_signer_grant("aa", "nip04_decrypt")); + assert!(vault.has_signer_grant("aa", "sign_event", Some(1))); + assert!(!vault.has_signer_grant("aa", "nip04_decrypt", None)); let json = serde_json::to_string(&vault).unwrap(); let mut loaded: Vault = serde_json::from_str(&json).unwrap(); - assert!(loaded.has_signer_grant("aa", "sign_event")); - assert!(loaded.has_signer_grant("bb", "sign_event")); + assert!(loaded.has_signer_grant("aa", "sign_event", Some(1))); + assert!(loaded.has_signer_grant("bb", "sign_event", Some(1))); assert!(loaded.revoke_signer_grant("aa", "sign_event")); assert!(!loaded.revoke_signer_grant("aa", "sign_event")); - assert!(!loaded.has_signer_grant("aa", "sign_event")); - assert!(loaded.has_signer_grant("bb", "sign_event")); + assert!(!loaded.has_signer_grant("aa", "sign_event", Some(1))); + assert!(loaded.has_signer_grant("bb", "sign_event", Some(1))); + } + + #[test] + fn signer_grants_are_kind_scoped() { + let mut vault = Vault::empty(); + // A grant made against a kind-1 request must not cover kind-3. + vault.grant_signer_method("aa", "sign_event", &[1]).unwrap(); + assert!(vault.has_signer_grant("aa", "sign_event", Some(1))); + assert!(!vault.has_signer_grant("aa", "sign_event", Some(3))); + // An unparseable kind (None) also falls back to the prompt. + assert!(!vault.has_signer_grant("aa", "sign_event", None)); + + // A legacy grant with no kind list (written before grants were + // kind-scoped) still covers every kind — stored vaults keep working. + vault.signer_grants.push(SignerGrant { + app_pubkey: "legacy".to_string(), + method: "sign_event".to_string(), + allowed_kinds: Vec::new(), + created_at: 0, + }); + assert!(vault.has_signer_grant("legacy", "sign_event", Some(1))); + assert!(vault.has_signer_grant("legacy", "sign_event", Some(30023))); + assert!(vault.has_signer_grant("legacy", "sign_event", None)); + + // Non-signing methods carry no kind dimension. + vault + .grant_signer_method("aa", "nip44_decrypt", &[]) + .unwrap(); + assert!(vault.has_signer_grant("aa", "nip44_decrypt", None)); } static COUNTER: AtomicU32 = AtomicU32::new(0); From fa59b63a1725dc6c629e76eaa8ac4faf127d5d13 Mon Sep 17 00:00:00 2001 From: Avi Date: Sun, 27 Sep 2026 22:43:42 -0500 Subject: [PATCH 06/17] fix(updates): augment spawned PATH so npm/cargo resolve from Electron The backend spawned by Electron inherits the desktop launcher's environment, not a login shell: ~/.cargo/bin and the mise/asdf shim dirs are missing, so 'Check for updates' failed with "'npm' was not found" even though both tools exist in a terminal. run() now sets PATH to the inherited value plus the well-known per-user tool dirs (~/.cargo/bin, ~/.local/bin, mise + asdf shims, /usr/local/bin), appended after the inherited entries so existing resolutions keep priority. The NotFound hint now says what to do. Verified live: update_check under env -i PATH=/usr/bin:/bin returns a full report (npm advisories + cargo updates) where it previously errored. Unit tests cover the pure PATH builder incl. missing-env fallback. --- src/updates.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/src/updates.rs b/src/updates.rs index ef75d9b..2217a15 100644 --- a/src/updates.rs +++ b/src/updates.rs @@ -72,6 +72,51 @@ fn frontend_dir() -> PathBuf { source_dir().join("frontend") } +/// PATH used for package-manager commands. +/// +/// When the backend is spawned by Electron its environment is the desktop +/// launcher's, NOT a login shell: `~/.cargo/bin` (cargo) and the node +/// version-manager bins/shims (npm) are missing, so the update commands +/// failed with "'npm' was not found" even though both exist in a terminal. +/// Augment the inherited PATH with the well-known per-user tool locations, +/// appended after the inherited entries: where a tool is already resolvable +/// on the inherited PATH that version wins, and entries that do not exist +/// are simply ignored by exec. +fn augmented_path() -> std::ffi::OsString { + let inherited = std::env::var_os("PATH"); + let home = std::env::var_os("HOME"); + build_augmented_path(inherited.as_deref(), home.as_deref()) +} + +/// Pure form of [`augmented_path`], testable without mutating the process +/// environment. +fn build_augmented_path( + inherited: Option<&std::ffi::OsStr>, + home: Option<&std::ffi::OsStr>, +) -> std::ffi::OsString { + let inherited_parts: Vec = inherited + .map(std::env::split_paths) + .map(|p| p.collect()) + .unwrap_or_default(); + let home = home.map(PathBuf::from); + let extra: Vec = [ + ".cargo/bin", + ".local/bin", + ".local/share/mise/shims", + ".asdf/shims", + "/usr/local/bin", + ] + .into_iter() + .filter_map(|rel| match rel.strip_prefix('/') { + // A leading slash marks an absolute system entry: use it verbatim. + Some(_) => Some(PathBuf::from(rel)), + None => home.as_ref().map(|h| h.join(rel)), + }) + .collect(); + std::env::join_paths(inherited_parts.into_iter().chain(extra)) + .unwrap_or_else(|_| std::ffi::OsString::from("/usr/local/bin:/usr/bin:/bin")) +} + /// Verify the app is running from its source checkout before shelling out. fn require_source_checkout() -> Result<(), AppError> { let manifest = source_dir().join("Cargo.toml"); @@ -88,13 +133,22 @@ fn require_source_checkout() -> Result<(), AppError> { /// Run a command with a timeout, capturing stdout/stderr separately. async fn run(dir: &Path, program: &str, args: &[&str]) -> Result { let mut command = Command::new(program); - command.current_dir(dir).args(args).kill_on_drop(true); + command + .current_dir(dir) + .args(args) + // See augmented_path(): Electron-spawned backends inherit a desktop + // PATH without the per-user tool dirs, and npm/cargo "don't exist". + .env("PATH", augmented_path()) + .kill_on_drop(true); let future = command.output(); match tokio::time::timeout(COMMAND_TIMEOUT, future).await { Ok(Ok(output)) => Ok(output), Ok(Err(err)) => { let hint = if err.kind() == std::io::ErrorKind::NotFound { - format!("'{program}' was not found on this computer.") + format!( + "'{program}' was not found on this computer. \ + Install it (or add it to the app's PATH) and try again." + ) } else { format!("Could not run '{program}': {err}") }; @@ -355,6 +409,29 @@ pub async fn apply() -> Result { mod tests { use super::*; + #[test] + fn augmented_path_appends_tool_dirs_after_inherited() { + let joined = build_augmented_path( + Some(std::ffi::OsStr::new("/usr/bin:/bin")), + Some(std::ffi::OsStr::new("/home/tester")), + ); + let joined = joined.to_string_lossy().into_owned(); + // Inherited entries keep priority. + assert!(joined.starts_with("/usr/bin:/bin:")); + // The dirs an Electron-spawned process is missing are now present. + assert!(joined.contains("/home/tester/.cargo/bin")); + assert!(joined.contains("/home/tester/.local/share/mise/shims")); + assert!(joined.contains("/usr/local/bin")); + } + + #[test] + fn augmented_path_survives_missing_env() { + // No PATH and no HOME: still a usable absolute system path. + let joined = build_augmented_path(None, None); + let joined = joined.to_string_lossy().into_owned(); + assert!(joined.contains("/usr/local/bin")); + } + #[test] fn parses_npm_outdated_entries() { let json = r#"{ From 118f5d37bd8609837c4d7ae5d7dc1da4778adc48 Mon Sep 17 00:00:00 2001 From: Avi Date: Sun, 27 Sep 2026 22:44:52 -0500 Subject: [PATCH 07/17] docs(checkpoint): permissions UI + updater PATH fix at fa59b63 (2026-09-27) --- CHECKPOINT-encryption.md | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/CHECKPOINT-encryption.md b/CHECKPOINT-encryption.md index 9f3d5d9..97f173b 100644 --- a/CHECKPOINT-encryption.md +++ b/CHECKPOINT-encryption.md @@ -1,3 +1,67 @@ +# Checkpoint — permissions UI + updater PATH fix (2026-09-27 night) + +## Where things are +- Project: `/home/avi/Projects/Keynctr`, branch `master` @ **`fa59b63`** + ("fix(updates): augment spawned PATH so npm/cargo resolve from Electron"). + Previous: `adbc7c2` (permissions UI), `98593cb` (checkpoint), `c89b31a`. +- Working tree: clean for tracked files. Untracked intentionally NOT + committed: `COSMIC_THEME.md`, `KeynectrAppIconPossibility02.jpeg`, + `deferred/`. +- Release binary rebuilt at fa59b63 (22:43, verified mtime — not a cache hit). +- NOTE: a running Electron app still serves the OLD backend + stale `dist/` + until relaunch; the running serve predates both commits. + +## What was completed +1. **Permissions are visible (Step 4, first slice).** `Nip46Status` now + carries the connection's declared `perms=` grant list and expiry. The + Signer Mode screen shows a **Permissions** panel on a live session: one + row per granted method ("Sign events — kinds 1, 30023"), or a plain + statement that the signer app (Amber) approves every request when no + grant list was declared. `frontend/src/lib/permissions.ts` holds the + shared label formatters. +2. **"Always allow" is now kind-scoped (was: method-wide, too broad).** + A `sign_event` grant records the kind of the request the user actually + approved; a kind-1 grant never covers a kind-3 request — uncovered kinds + fall back to the approval prompt. Legacy kind-less grants keep their + all-kinds meaning (stored vaults keep working; new grants are never + created kind-less). Enforced in BOTH `bunker.rs` (bunker mode) and + `nip46_client.rs` (client mode) via `Vault::has_signer_grant(peer, + method, event_kind)`. The Signer screen's grants list renders the human + label with kind scope. +3. **Check-for-updates fixed.** The Electron-spawned backend inherited the + desktop launcher's PATH (no `~/.cargo/bin`, no mise/asdf shims), so + `npm`/`cargo` "didn't exist". `updates.rs::run()` now appends the + well-known per-user tool dirs to the inherited PATH (inherited wins on + conflicts; missing dirs ignored). Verified live under + `env -i PATH=/usr/bin:/bin`: `update_check` returns a full report. + +## Commits added +- `adbc7c2` feat(signer): permissions UI — declared grants surfaced, always-allow kind-scoped +- `fa59b63` fix(updates): augment spawned PATH so npm/cargo resolve from Electron + +## Verification (all green at fa59b63) +- Rust: `cargo test` 219 unit + 6 e2e (NEW: `signer_grants_are_kind_scoped`, + two `augmented_path` tests); `cargo clippy --all-targets` 0; `cargo fmt + --check` clean; `cargo build --release` rebuilt 22:43. +- Frontend: `npm test` 135 (10 new: `permissions.test.ts` unit + 2 + SignerModeScreen permission-panel tests), `typecheck`, `lint`, + `format:check`, `build`, `electron:build` all green. + +## Deferred / next steps +- Live eyeball: relaunch the app, pair with a `perms=`-carrying client (or + Amber) and check the Permissions panel; approve kind-1 "Always allow", + then send a kind-3 request and confirm it PROMPTS (kind scope). +- Two-account live pass from the previous checkpoint still open (pair + account B in Amber, switch back and forth). +- Publish kind-0 to primal/damus (one Amber approval). +- Step 4 remainder (if wanted): interactive grant editing in the approval + modal (approve-with-narrowing UI); today the modal is Approve / Always + allow (kind-scoped) / Reject. +- Step 5 (KDF upgrade m=64MiB/t=3 + vault header versioning), Step 6 (undo + history), Step 7 (rename/hygiene incl. `homepage` URL). + +--- + # Checkpoint — multi-account signer switching (Option A) (2026-09-27 eve) ## Where things are From 15fa331f46b848a8993258b9bb8899cbdbdbf11a Mon Sep 17 00:00:00 2001 From: Avi Date: Mon, 28 Sep 2026 08:30:48 -0500 Subject: [PATCH 08/17] chore(deps): apply dependency updates from the in-app updater run Applying npm audit fix + npm update + cargo update via the (fixed) updater: clears the high-severity js-yaml advisory (maxTotalMergeKeys CPU use on empty merge sources). Full suites verified green after the bump: 219 Rust unit + 6 e2e, 135 frontend, vite build clean. --- Cargo.lock | 297 ++++++++------- frontend/package-lock.json | 721 ++++++++++++++++++++----------------- 2 files changed, 556 insertions(+), 462 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c2df61..47804f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,7 +214,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -292,12 +292,12 @@ dependencies = [ [[package]] name = "bitcoin-consensus-encoding" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6712f9c6fd6785b3b270884e57c441c403dc5d7e19ca45368c97c7a1de3000ec" +checksum = "9daa31138eb443d5751b207f3f64154e2bb09cd59960562ccc7a7112be38147f" dependencies = [ - "bitcoin-internals", - "hex-conservative 1.2.0", + "bitcoin-internals 0.7.0", + "hex-conservative 1.3.0", "serde", ] @@ -307,6 +307,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" +[[package]] +name = "bitcoin-internals" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8bea3a9f0cfece4564e37cb49a38cc245ca5184719e50d7d0dda3268722c4e2" + [[package]] name = "bitcoin-io" version = "0.1.101" @@ -323,7 +329,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" dependencies = [ "bitcoin-io", - "hex-conservative 0.2.2", + "hex-conservative 0.2.3", ] [[package]] @@ -333,16 +339,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5304e53726dbe5f93141535e102ed97b5bf4714fbecefdda8f9fb98d7fdaff0e" dependencies = [ "bitcoin-consensus-encoding", - "bitcoin-internals", - "hex-conservative 1.2.0", + "bitcoin-internals 0.6.0", + "hex-conservative 1.3.0", "serde", ] [[package]] name = "bitflags" -version = "2.13.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "blake2" @@ -440,9 +452,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.4" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" dependencies = [ "find-msvc-tools", "shlex", @@ -450,9 +462,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "chacha20" @@ -562,9 +574,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crypto-common" @@ -610,6 +622,46 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.1", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.21", +] + [[package]] name = "digest" version = "0.10.7" @@ -641,7 +693,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -715,10 +767,12 @@ dependencies = [ [[package]] name = "faster-hex" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +checksum = "04839bdf9d8c10f66806fad16b852fc72aab80873aebc3cb69d85b4fa41543ed" dependencies = [ + "autocfg", + "defmt 0.3.100", "heapless", "serde", ] @@ -731,9 +785,9 @@ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "find-msvc-tools" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "form_urlencoded" @@ -813,7 +867,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -951,18 +1005,18 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +checksum = "db3fef046dca3ca91ee1408a8c1b80ab777e80a4d308d1bf4e7adb3fcb047e08" dependencies = [ "arrayvec", ] [[package]] name = "hex-conservative" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35431185f361ccf3ffc58254628af5f1f5d5f28531da2e02e5d6c82bbc282a10" +checksum = "271e0d19bcb473b6675739a2b536076b24a082316cb5199ad918edce10c599e8" dependencies = [ "arrayvec", ] @@ -1003,9 +1057,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "typenum", ] @@ -1116,9 +1170,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.1" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown", @@ -1152,9 +1206,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ "cfg-if", "futures-util", @@ -1241,9 +1295,9 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lru" -version = "0.18.2" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" +checksum = "ef9ac18847474e638e3702b76c65d4eb93428471a74778ef0f1be711717f89b5" [[package]] name = "memchr" @@ -1262,9 +1316,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "wasi", @@ -1279,9 +1333,9 @@ checksum = "81c353b400a5503efdcf398f11a83fb7aa84f59f5d76fc4bf5bbc1e4f5366caa" [[package]] name = "nostr" -version = "0.45.3" +version = "0.45.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0ba32ce43631188586469ba1a4c40bcfa63641f1ad5de89ef77f74d801cc17a" +checksum = "38ca5c25a6df8d4d78fdf39b42792438f6ce22b4809ccbfdb27582ed9ca45407" dependencies = [ "aes 0.8.4", "base64", @@ -1293,7 +1347,7 @@ dependencies = [ "chacha20poly1305", "faster-hex", "opaquerr", - "rand 0.10.2", + "rand 0.10.3", "secp256k1", "serde", "serde_json", @@ -1305,9 +1359,9 @@ dependencies = [ [[package]] name = "nostr-database" -version = "0.45.1" +version = "0.45.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b1fdb9fcba732e32719662afad1b267e50322dbe89e506017ec13f24361bddf" +checksum = "309950383c9854ca413d195705400e78b1376aedc48f3256754a86c609c047e8" dependencies = [ "nostr", "opaquerr", @@ -1315,9 +1369,9 @@ dependencies = [ [[package]] name = "nostr-gossip" -version = "0.45.0" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa07539e52a71cb91fe0d693facaa298f03fcf9edcd66a521094e18e286e2336" +checksum = "4ea822fd48ff6b84b81ddf84169e6edf1dc0bc9b312c8e41685b2278debaac3d" dependencies = [ "nostr", "opaquerr", @@ -1325,9 +1379,9 @@ dependencies = [ [[package]] name = "nostr-sdk" -version = "0.45.2" +version = "0.45.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245f8642b10feecb0a40739a886ca1850e3be4e35dc6592b806ec437403ab9c9" +checksum = "db717044f755b5ed9be53cacb59660c36efbe578bde870151a24d6bead3cb218" dependencies = [ "async-utility", "async-wsocket", @@ -1339,7 +1393,7 @@ dependencies = [ "nostr-database", "nostr-gossip", "opaquerr", - "rand 0.10.2", + "rand 0.10.3", "tokio", "tokio-stream", "tracing", @@ -1626,9 +1680,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ "getrandom 0.4.3", "rand_core 0.10.1", @@ -1684,7 +1738,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.2", ] [[package]] @@ -1743,21 +1797,21 @@ dependencies = [ [[package]] name = "rtoolbox" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" +checksum = "9a1efe12a1469752d0e6ff5ebec0b6ef4924cc5c4c71046b0ec730040535819d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys", @@ -1766,9 +1820,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "once_cell", "ring", @@ -1855,7 +1909,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.13.2", "core-foundation", "core-foundation-sys", "libc", @@ -1899,7 +1953,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -1923,7 +1977,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -1983,9 +2037,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" [[package]] name = "socket2" @@ -2022,9 +2076,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", @@ -2033,13 +2087,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] @@ -2066,11 +2120,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl 2.0.20", + "thiserror-impl 2.0.21", ] [[package]] @@ -2086,13 +2140,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -2107,18 +2161,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" @@ -2154,14 +2199,14 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ "rustls", "tokio", @@ -2231,9 +2276,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.13+spec-1.1.0" +version = "0.25.15+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" dependencies = [ "indexmap", "toml_datetime", @@ -2296,7 +2341,7 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror 2.0.20", + "thiserror 2.0.21", "utf-8", ] @@ -2319,9 +2364,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] name = "unicode-normalization" @@ -2381,9 +2426,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.25.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -2414,9 +2459,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -2427,19 +2472,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.77" +version = "0.4.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" dependencies = [ "js-sys", + "tokio", "wasm-bindgen", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2447,31 +2493,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.104" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" dependencies = [ "js-sys", "wasm-bindgen", @@ -2523,15 +2569,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -2639,13 +2676,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "synstructure", ] @@ -2704,7 +2741,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", "zbus_names", "zvariant", "zvariant_utils", @@ -2732,18 +2769,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" dependencies = [ "proc-macro2", "quote", @@ -2761,13 +2798,13 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "synstructure", ] @@ -2807,7 +2844,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -2840,7 +2877,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", "zvariant_utils", ] @@ -2853,6 +2890,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 3.0.4", + "syn 3.0.6", "winnow", ] diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d59b4a1..1d88fdc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -250,9 +250,9 @@ "license": "MIT" }, "node_modules/@electron/asar/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -439,9 +439,9 @@ "license": "MIT" }, "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "dev": true, "license": "MIT", "dependencies": { @@ -449,9 +449,9 @@ } }, "node_modules/@electron/universal/node_modules/fs-extra": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", - "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", "dev": true, "license": "MIT", "dependencies": { @@ -502,9 +502,9 @@ } }, "node_modules/@electron/windows-sign/node_modules/fs-extra": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", - "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", "dev": true, "license": "MIT", "optional": true, @@ -583,9 +583,9 @@ "license": "MIT" }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -633,9 +633,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", - "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", "dev": true, "license": "MIT", "dependencies": { @@ -645,7 +645,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.3.0", + "js-yaml": "^4.3.2", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -656,6 +656,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -664,9 +681,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -674,6 +691,13 @@ "concat-map": "0.0.1" } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -804,9 +828,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, @@ -905,9 +929,9 @@ } }, "node_modules/@noble/hashes": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", - "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", "dev": true, "license": "MIT", "engines": { @@ -918,19 +942,19 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.146.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz", - "integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==", + "version": "0.151.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.151.0.tgz", + "integrity": "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==", "dev": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@peculiar/asn1-schema": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", - "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.10.0.tgz", + "integrity": "sha512-GhokD41lV4gQrrLYm3wCkHfBOnJrnhDMgt4XeMW8gzfE1UdJqIuSwsE+ggf82XBjUXRJylcm+KIGQFa4utIVLw==", "dev": true, "license": "MIT", "dependencies": { @@ -983,9 +1007,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz", - "integrity": "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz", + "integrity": "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==", "cpu": [ "arm" ], @@ -1000,9 +1024,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz", - "integrity": "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.11.tgz", + "integrity": "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==", "cpu": [ "arm64" ], @@ -1017,9 +1041,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.5.tgz", - "integrity": "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.11.tgz", + "integrity": "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==", "cpu": [ "arm64" ], @@ -1034,9 +1058,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.5.tgz", - "integrity": "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.11.tgz", + "integrity": "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==", "cpu": [ "x64" ], @@ -1051,9 +1075,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.5.tgz", - "integrity": "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.11.tgz", + "integrity": "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==", "cpu": [ "x64" ], @@ -1068,9 +1092,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.5.tgz", - "integrity": "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.11.tgz", + "integrity": "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==", "cpu": [ "arm" ], @@ -1085,13 +1109,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.5.tgz", - "integrity": "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.11.tgz", + "integrity": "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1102,13 +1129,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.5.tgz", - "integrity": "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.11.tgz", + "integrity": "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1119,13 +1149,16 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.5.tgz", - "integrity": "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.11.tgz", + "integrity": "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1136,13 +1169,16 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.5.tgz", - "integrity": "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.11.tgz", + "integrity": "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1153,13 +1189,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.5.tgz", - "integrity": "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.11.tgz", + "integrity": "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1170,13 +1209,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.5.tgz", - "integrity": "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.11.tgz", + "integrity": "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1187,9 +1229,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.5.tgz", - "integrity": "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.11.tgz", + "integrity": "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==", "cpu": [ "arm64" ], @@ -1204,9 +1246,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.5.tgz", - "integrity": "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.11.tgz", + "integrity": "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==", "cpu": [ "arm64" ], @@ -1221,9 +1263,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.5.tgz", - "integrity": "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.11.tgz", + "integrity": "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==", "cpu": [ "x64" ], @@ -1338,9 +1380,9 @@ } }, "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.2.tgz", + "integrity": "sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1385,9 +1427,9 @@ "license": "MIT" }, "node_modules/@testing-library/react": { - "version": "16.3.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", - "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.3.tgz", + "integrity": "sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==", "dev": true, "license": "MIT", "dependencies": { @@ -1413,9 +1455,9 @@ } }, "node_modules/@testing-library/user-event": { - "version": "14.6.6", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.6.tgz", - "integrity": "sha512-Jbs9FpkkIDw8FgSc6kOVsOv8JuuqGAL7J4X1oot77JxAoDlkNn2GRkd0aYRVuQ+pVQAiHWVkE4rX/dkF5fBiCw==", + "version": "14.6.7", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.7.tgz", + "integrity": "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==", "dev": true, "license": "MIT", "engines": { @@ -1523,13 +1565,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", - "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.3.tgz", + "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "undici-types": "~8.9.0" } }, "node_modules/@types/prop-types": { @@ -1581,17 +1623,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", - "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.1.tgz", + "integrity": "sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/type-utils": "8.67.0", - "@typescript-eslint/utils": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/type-utils": "8.70.1", + "@typescript-eslint/utils": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1604,15 +1646,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.67.0", + "@typescript-eslint/parser": "^8.70.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", - "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.10.tgz", + "integrity": "sha512-HpbUakT7xp5miBUywCHf36ZEuAJNklBJDDsGpUIjMzOSmM8ELSfA9Sa/QDPeNeqeoN31u+UTCkL4klCOVvRm4Q==", "dev": true, "license": "MIT", "engines": { @@ -1620,16 +1662,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", - "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.70.1.tgz", + "integrity": "sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3" }, "engines": { @@ -1645,14 +1687,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", - "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.1.tgz", + "integrity": "sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.67.0", - "@typescript-eslint/types": "^8.67.0", + "@typescript-eslint/tsconfig-utils": "^8.70.1", + "@typescript-eslint/types": "^8.70.1", "debug": "^4.4.3" }, "engines": { @@ -1667,14 +1709,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", - "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.1.tgz", + "integrity": "sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0" + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1685,9 +1727,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", - "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz", + "integrity": "sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==", "dev": true, "license": "MIT", "engines": { @@ -1702,15 +1744,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", - "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.70.1.tgz", + "integrity": "sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1", + "@typescript-eslint/utils": "8.70.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1727,9 +1769,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", - "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.1.tgz", + "integrity": "sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==", "dev": true, "license": "MIT", "engines": { @@ -1741,16 +1783,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", - "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz", + "integrity": "sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.67.0", - "@typescript-eslint/tsconfig-utils": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/project-service": "8.70.1", + "@typescript-eslint/tsconfig-utils": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1769,16 +1811,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", - "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.1.tgz", + "integrity": "sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0" + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1793,13 +1835,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", - "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.1.tgz", + "integrity": "sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/types": "8.70.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1824,9 +1866,9 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.0.tgz", - "integrity": "sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", "dev": true, "license": "MIT", "dependencies": { @@ -2020,16 +2062,16 @@ } }, "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -2164,23 +2206,6 @@ "semver": "bin/semver.js" } }, - "node_modules/app-builder-lib/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/app-builder-lib/node_modules/ci-info": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", @@ -2207,23 +2232,6 @@ "node": ">=6" } }, - "node_modules/app-builder-lib/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/app-builder-lib/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/app-builder-lib/node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -2257,22 +2265,6 @@ "node": ">= 4.0.0" } }, - "node_modules/app-builder-lib/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2404,9 +2396,9 @@ "optional": true }, "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2719,6 +2711,29 @@ "node": ">= 8" } }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -2949,9 +2964,9 @@ "license": "MIT" }, "node_modules/dir-compare/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -3063,9 +3078,9 @@ } }, "node_modules/electron": { - "version": "43.4.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-43.4.1.tgz", - "integrity": "sha512-5b+EuiwkgG5iRcsEL34rimgRpkYp15SsfZOa0pC5kXs0Tb82TH4n95rpQzTZa7yRCbA7tm0WoEbuBL6NaAhAcA==", + "version": "43.7.5", + "resolved": "https://registry.npmjs.org/electron/-/electron-43.7.5.tgz", + "integrity": "sha512-AR+OLH/8QAwx6Lz6mhaDQhesbjC/Xh2WOiFSI/CDFU6FKX/qJdBg65bEUn+MLvbvIi/LlaRQeLljnUcKb1KS0A==", "dev": true, "license": "MIT", "dependencies": { @@ -3199,19 +3214,19 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "24.13.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", - "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "version": "24.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.0.tgz", + "integrity": "sha512-zY+5tKxXdhGh1PYI0ac+7juvEu4OI6vWtVVoj5i2m42jxAY1U+zHGt6QCyOFwykdP62sM3MJ9stoYYUw5aCWew==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/electron/node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, @@ -3455,6 +3470,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3463,9 +3495,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -3473,6 +3505,13 @@ "concat-map": "0.0.1" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -3599,9 +3638,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz", - "integrity": "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", "dev": true, "funding": [ { @@ -3664,9 +3703,9 @@ "license": "MIT" }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "dev": true, "license": "MIT", "dependencies": { @@ -3895,9 +3934,9 @@ "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -4294,11 +4333,14 @@ } }, "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/jake": { "version": "10.9.4", @@ -4335,9 +4377,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -4406,9 +4448,9 @@ "license": "MIT" }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, @@ -4627,6 +4669,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4648,6 +4693,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4669,6 +4717,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4690,6 +4741,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4981,9 +5035,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "dev": true, "funding": [ { @@ -5007,9 +5061,9 @@ "license": "MIT" }, "node_modules/node-abi": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.33.0.tgz", - "integrity": "sha512-vLBWCKb+7LWsX+TbfzWOkw0W81m377tyx3hOweBTjO43CXZnRGS1/JPWs20fr0PgZyDXk6ROYrylsEycK8raDA==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.35.0.tgz", + "integrity": "sha512-ymk4aIzxdPopw2giv8Fs1Ec6vybGkjmyxUwVqhkI4MCy2tVfXdkOGGWieWVjL0THgH+7a8lRdevyupoYj3Js/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5075,9 +5129,9 @@ } }, "node_modules/node-gyp/node_modules/undici": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", - "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", + "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", "dev": true, "license": "MIT", "engines": { @@ -5137,9 +5191,9 @@ } }, "node_modules/nostr-tools": { - "version": "2.25.1", - "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.25.1.tgz", - "integrity": "sha512-k/yCjpjHR18n9E6kCh1MdlP+fGZnP9UkuIDt1cHF87jqAE6ohOnZGFuQXPfWhDaRzNj9TQgJZPAPkCqOylqtAg==", + "version": "2.25.2", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.25.2.tgz", + "integrity": "sha512-7JAx+brEPmqGz1yzaK9MOxvis/Nl1B38j9hVsBSRDMRmxk5XSc0+5LlPEYbzeiUyk41RURBDcfuMzmaT754HAg==", "license": "Unlicense", "dependencies": { "@noble/ciphers": "2.1.1", @@ -5178,9 +5232,9 @@ "license": "MIT" }, "node_modules/nwsapi": { - "version": "2.2.24", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", - "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "version": "2.2.28", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.28.tgz", + "integrity": "sha512-IlVB7OS7qrOsVYlpnFIkETjMwT9jwvmocJmmM+GZU/PAB3uGi9Ezd7vcWhWBUnSc0ya4ppmQITOyP1ez9gg8cg==", "dev": true, "license": "MIT" }, @@ -5196,9 +5250,9 @@ } }, "node_modules/obug": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", - "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -5386,13 +5440,16 @@ } }, "node_modules/pkijs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", - "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.1.tgz", + "integrity": "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==", "dev": true, "license": "BSD-3-Clause", + "workspaces": [ + "website" + ], "dependencies": { - "@noble/hashes": "1.4.0", + "@noble/hashes": "1.8.0", "asn1js": "^3.0.6", "bytestreamjs": "^2.0.1", "pvtsutils": "^1.3.6", @@ -5404,13 +5461,13 @@ } }, "node_modules/pkijs/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", "engines": { - "node": ">= 16" + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -5441,9 +5498,9 @@ } }, "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dev": true, "funding": [ { @@ -5461,7 +5518,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.17", + "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5510,9 +5567,9 @@ } }, "node_modules/prettier": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", - "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", "dev": true, "license": "MIT", "bin": { @@ -5988,13 +6045,13 @@ } }, "node_modules/rolldown": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.5.tgz", - "integrity": "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", + "integrity": "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.146.0", + "@oxc-project/types": "=0.151.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6004,21 +6061,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.5", - "@rolldown/binding-android-arm64": "1.2.5", - "@rolldown/binding-darwin-arm64": "1.2.5", - "@rolldown/binding-darwin-x64": "1.2.5", - "@rolldown/binding-freebsd-x64": "1.2.5", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.5", - "@rolldown/binding-linux-arm64-gnu": "1.2.5", - "@rolldown/binding-linux-arm64-musl": "1.2.5", - "@rolldown/binding-linux-ppc64-gnu": "1.2.5", - "@rolldown/binding-linux-s390x-gnu": "1.2.5", - "@rolldown/binding-linux-x64-gnu": "1.2.5", - "@rolldown/binding-linux-x64-musl": "1.2.5", - "@rolldown/binding-openharmony-arm64": "1.2.5", - "@rolldown/binding-win32-arm64-msvc": "1.2.5", - "@rolldown/binding-win32-x64-msvc": "1.2.5" + "@rolldown/binding-android-arm-eabi": "1.2.11", + "@rolldown/binding-android-arm64": "1.2.11", + "@rolldown/binding-darwin-arm64": "1.2.11", + "@rolldown/binding-darwin-x64": "1.2.11", + "@rolldown/binding-freebsd-x64": "1.2.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", + "@rolldown/binding-linux-arm64-gnu": "1.2.11", + "@rolldown/binding-linux-arm64-musl": "1.2.11", + "@rolldown/binding-linux-ppc64-gnu": "1.2.11", + "@rolldown/binding-linux-s390x-gnu": "1.2.11", + "@rolldown/binding-linux-x64-gnu": "1.2.11", + "@rolldown/binding-linux-x64-musl": "1.2.11", + "@rolldown/binding-openharmony-arm64": "1.2.11", + "@rolldown/binding-win32-arm64-msvc": "1.2.11", + "@rolldown/binding-win32-x64-msvc": "1.2.11" } }, "node_modules/rrweb-cssom": { @@ -6417,9 +6474,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", - "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, "license": "MIT", "engines": { @@ -6591,16 +6648,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz", - "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.70.1.tgz", + "integrity": "sha512-AcWG7KDjZ2THNXsgwttMaGmzVi0VFRlFYfqFHYQRbDpF3owuYbuiL8c7UUrd2k8s3PoSfIQrWfrGXfcElrWLYA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.67.0", - "@typescript-eslint/parser": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/utils": "8.67.0" + "@typescript-eslint/eslint-plugin": "8.70.1", + "@typescript-eslint/parser": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1", + "@typescript-eslint/utils": "8.70.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6615,9 +6672,9 @@ } }, "node_modules/undici": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", - "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", + "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", "dev": true, "license": "MIT", "optional": true, @@ -6626,9 +6683,9 @@ } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, @@ -6696,16 +6753,16 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", - "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.1.tgz", + "integrity": "sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.26", - "rolldown": "~1.2.4", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.9", "tinyglobby": "^0.2.17" }, "bin": { @@ -6722,7 +6779,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -6939,19 +6996,19 @@ } }, "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { - "node-which": "bin/node-which" + "node-which": "bin/which.js" }, "engines": { - "node": ">= 8" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/which-module": { @@ -7013,9 +7070,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", - "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", "dev": true, "license": "MIT", "engines": { From add956abdd4611ed6fa30b78e3951ac0652c90b8 Mon Sep 17 00:00:00 2001 From: Avi Date: Mon, 28 Sep 2026 08:54:13 -0500 Subject: [PATCH 09/17] =?UTF-8?q?feat(theme):=20add=20Workshop=20theme=20?= =?UTF-8?q?=E2=80=94=20Cybernetic=20Workshop=20identity=20from=20Moi=20DES?= =?UTF-8?q?IGN.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Warm paper (#f3efe6), near-black ink, hairline borders, one pine accent (#215c48), serif display headings, Moi radii (14/9). Tokens verified rendering live via computed styles on the built shell: body bg #f3efe6, card #faf7f0, border #d9d1c3, h2 Iowan/Palatino 500. Settings -> Appearance -> 'Workshop — Cybernetic'. --- frontend/src/lib/types.ts | 2 +- frontend/src/screens/SettingsScreen.tsx | 1 + frontend/src/styles.css | 64 +++++++++++++++++++++++++ src/settings.rs | 4 ++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index a255dd9..ab62400 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -1,5 +1,5 @@ export type Theme = - 'light' | 'dark' | 'glass' | 'neon' | 'impeccable' | 'impeccable-dark' | 'cosmic'; + 'light' | 'dark' | 'glass' | 'neon' | 'impeccable' | 'impeccable-dark' | 'cosmic' | 'workshop'; /** 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 9be95c9..e7b6a2a 100644 --- a/frontend/src/screens/SettingsScreen.tsx +++ b/frontend/src/screens/SettingsScreen.tsx @@ -109,6 +109,7 @@ export function SettingsScreen() { +