From 349e9d12f82a714dcdfaafbd3cdfefafc62a8fb0 Mon Sep 17 00:00:00 2001
From: Jure <44338+hoornet@users.noreply.github.com>
Date: Thu, 21 May 2026 21:19:55 +0200
Subject: [PATCH] polish: onboarding step indicator + safe backup escape hatch
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Onboarding Shell: larger accent VEGA wordmark + step-progress dots
across the create -> backup -> interests path.
- Backup step gains an "I'll back up my key later" escape hatch. It runs
the SAME keychain-saving login as confirming (skips only the checkbox)
— wiring it to onComplete alone would have lost the generated key.
- Settings -> Identity now reveals the secret key (nsec): reveal/hide/
copy via the existing load_nsec keychain command, hidden for read-only
and remote-signer accounts. This makes the escape hatch honest — its
note truthfully points users to where they can retrieve the key, and
fills a real gap (the nsec was previously unreachable after onboarding).
---
src/components/onboarding/OnboardingFlow.tsx | 55 ++++++++++++++++++--
src/components/shared/SettingsView.tsx | 48 ++++++++++++++++-
2 files changed, 97 insertions(+), 6 deletions(-)
diff --git a/src/components/onboarding/OnboardingFlow.tsx b/src/components/onboarding/OnboardingFlow.tsx
index 25f54fa..6f01796 100644
--- a/src/components/onboarding/OnboardingFlow.tsx
+++ b/src/components/onboarding/OnboardingFlow.tsx
@@ -10,11 +10,36 @@ interface OnboardingFlowProps {
// ─── Shared layout ───────────────────────────────────────────────────────────
-function Shell({ children }: { children: React.ReactNode }) {
+function Shell({
+ children,
+ step,
+ totalSteps,
+}: {
+ children: React.ReactNode;
+ step?: number;
+ totalSteps?: number;
+}) {
return (
-
VEGA
+ {/* Logo wordmark */}
+
VEGA
+
+ {/* Step dots — shown only for the linear create→backup→interests path */}
+ {step !== undefined && totalSteps !== undefined && (
+
@@ -70,7 +95,7 @@ function InterestsStep({ onComplete }: { onComplete: () => void }) {
};
return (
-
+ What are you into?
Pick a few topics to get your feed started. You can always change this later.
@@ -157,7 +182,7 @@ function CreateStep({ onNext }: { onNext: (signer: NDKPrivateKeySigner) => void
};
return (
-
+ Your identity is ready.
We generated a unique key pair for you. Your public key is
@@ -215,8 +240,18 @@ function BackupStep({ signer, onComplete }: { signer: NDKPrivateKeySigner; onCom
onComplete();
};
+ // "Back up later" escape hatch — runs the SAME login as confirming (this is
+ // what saves the key to the keychain and signs in); it only skips the
+ // checkbox gate. The key stays retrievable via Settings → Identity.
+ const handleSkipBackup = async () => {
+ setSaving(true);
+ await loginWithNsec(signer.nsec);
+ setSaving(false);
+ onComplete();
+ };
+
return (
-
+ Save your secret key.
Your secret key is the only way to recover your
@@ -272,6 +307,16 @@ function BackupStep({ signer, onComplete }: { signer: NDKPrivateKeySigner; onCom
>
{saving ? "Setting up…" : "Start using Vega"}
+
+
+ You can reveal your key any time in Settings → Identity.
+
);
}
diff --git a/src/components/shared/SettingsView.tsx b/src/components/shared/SettingsView.tsx
index b130111..37db34a 100644
--- a/src/components/shared/SettingsView.tsx
+++ b/src/components/shared/SettingsView.tsx
@@ -1,6 +1,7 @@
import { useState, useEffect } from "react";
import { save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs";
+import { invoke } from "@tauri-apps/api/core";
import { useUserStore } from "../../stores/user";
import { useUIStore } from "../../stores/ui";
import { useWoTStore } from "../../stores/wot";
@@ -206,8 +207,18 @@ function WoTSection() {
}
function IdentitySection() {
- const { npub, loggedIn } = useUserStore();
+ const { npub, loggedIn, pubkey } = useUserStore();
const [copied, setCopied] = useState(false);
+ const [nsec, setNsec] = useState(null);
+ const [revealed, setRevealed] = useState(false);
+ const [nsecCopied, setNsecCopied] = useState(false);
+
+ // Load the secret key from the OS keychain. Returns null for read-only
+ // (npub) and remote-signer (bunker) accounts — the nsec row is hidden then.
+ useEffect(() => {
+ if (!pubkey) return;
+ invoke("load_nsec", { pubkey }).then(setNsec).catch(() => setNsec(null));
+ }, [pubkey]);
if (!loggedIn || !npub) {
return (
@@ -225,6 +236,14 @@ function IdentitySection() {
});
};
+ const handleCopyNsec = () => {
+ if (!nsec) return;
+ navigator.clipboard.writeText(nsec).then(() => {
+ setNsecCopied(true);
+ setTimeout(() => setNsecCopied(false), 2000);
+ });
+ };
+
return (