From a0133369b6baa6ebe57e4e87925b27cbfddf2e4f Mon Sep 17 00:00:00 2001 From: LC mac Date: Sun, 8 Feb 2026 23:36:33 +0800 Subject: [PATCH] feat(app): Add Create Seed tab and enhance Seed Blender workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This major update introduces a new "Create" tab for generating fresh BIP39 mnemonic seeds and significantly improves the entire application workflow, particularly the interaction with the Seed Blender. **✨ New Features & Enhancements** * **Create Seed Tab**: * Add a new "Create" tab as the default view for generating 12 or 24-word BIP39 seeds. * Implement a destination selector, allowing users to send the newly generated seed directly to the "Backup" tab for encryption or to the "Seed Blender" for advanced operations. * The UI automatically switches to the chosen destination tab after generation for a seamless workflow. * **Seed Blender Integration**: * Generated seeds sent to the Seed Blender are now automatically added to the list of inputs. * The Seed Blender's state is now preserved when switching between tabs, preventing data loss and allowing users to accumulate seeds from the Create tab. * **Global Reset Functionality**: * A "Reset All" button has been added to the main header for a global application reset. * This action clears all component states (including the Seed Blender's internal state), passwords, generated data, and the in-memory session key, returning the app to a fresh initial state. * **UI/UX Polish**: * The "Use This Seed for Backup" button in the Seed Blender has been restyled to match the application's cyberpunk aesthetic and its text clarified. * The "Create" tab UI is cleared automatically after a seed is generated and the user is navigated away, ensuring a clean slate for the next use. **🔒 Security Fixes** * **Auto-Clear Passwords**: Password and passphrase fields in both the "Backup" and "Restore" tabs are now automatically cleared from the UI and state after a successful encryption or decryption operation. This prevents sensitive data from lingering in the application. * **Robust Seed Generation**: The seed generation process now uses the secure `crypto.getRandomValues` Web API to generate entropy before converting it to a mnemonic. **🐛 Bug Fixes** * **Seed Blender State**: * Fixed a critical bug where the Seed Blender's internal state was lost when switching tabs. The component is now kept mounted but hidden via CSS. * Resolved an issue where a seed sent from the "Create" tab could be added multiple times to the blender. A `useRef` guard now prevents duplicates. * Corrected a race condition where transferring a blended seed to the "Backup" tab would clear the blender's state before the data could be used. The auto-clear has been removed in favor of the manual "Reset All" button. --- package.json | 2 +- src/App.tsx | 459 ++++++++++++++++++------- src/components/Header.tsx | 110 +++--- src/components/SeedBlender.tsx | 592 ++++++++++++++++++--------------- 4 files changed, 723 insertions(+), 440 deletions(-) diff --git a/package.json b/package.json index 64d275f..ab9de6e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "seedpgp-web", "private": true, - "version": "1.4.4", + "version": "1.4.5", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.tsx b/src/App.tsx index df4ad5a..fa1057b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,7 +42,7 @@ interface ClipboardEvent { } function App() { - const [activeTab, setActiveTab] = useState<'backup' | 'restore' | 'seedblender'>('backup'); + const [activeTab, setActiveTab] = useState<'create' | 'backup' | 'restore' | 'seedblender'>('create'); const [mnemonic, setMnemonic] = useState(''); const [backupMessagePassword, setBackupMessagePassword] = useState(''); const [restoreMessagePassword, setRestoreMessagePassword] = useState(''); @@ -76,7 +76,12 @@ function App() { const [encryptionMode, setEncryptionMode] = useState<'pgp' | 'krux' | 'seedqr'>('pgp'); const [seedQrFormat, setSeedQrFormat] = useState<'standard' | 'compact'>('standard'); + const [generatedSeed, setGeneratedSeed] = useState(''); + const [seedWordCount, setSeedWordCount] = useState<12 | 24>(24); + const [seedDestination, setSeedDestination] = useState<'backup' | 'seedblender'>('backup'); const [detectedMode, setDetectedMode] = useState(null); + const [seedForBlender, setSeedForBlender] = useState(''); + const [blenderResetKey, setBlenderResetKey] = useState(0); const SENSITIVE_PATTERNS = ['key', 'mnemonic', 'seed', 'private', 'secret', 'pgp', 'password']; @@ -246,6 +251,45 @@ function App() { } }; + const generateNewSeed = async () => { + try { + setLoading(true); + setError(''); + + // Generate random entropy + const entropyLength = seedWordCount === 12 ? 16 : 32; // 128 bits for 12 words, 256 for 24 + const entropy = new Uint8Array(entropyLength); + crypto.getRandomValues(entropy); + + // Convert to mnemonic using your existing lib + const { entropyToMnemonic } = await import('./lib/seedblend'); + const newMnemonic = await entropyToMnemonic(entropy); + + setGeneratedSeed(newMnemonic); + + // Set mnemonic for backup if that's the destination + if (seedDestination === 'backup') { + setMnemonic(newMnemonic); + } else if (seedDestination === 'seedblender') { + setSeedForBlender(newMnemonic); + } + + // Auto-switch to chosen destination after generation + setTimeout(() => { + setActiveTab(seedDestination); + // Reset Create tab state after switching + setTimeout(() => { + setGeneratedSeed(''); + }, 300); + }, 1500); + + } catch (e) { + setError(e instanceof Error ? e.message : 'Seed generation failed'); + } finally { + setLoading(false); + } + }; + const handleBackup = async () => { setLoading(true); setError(''); @@ -298,6 +342,10 @@ function App() { const blob = await encryptJsonToBlob({ mnemonic, timestamp: Date.now() }); setEncryptedMnemonicCache(blob); setMnemonic(''); // Clear plaintext mnemonic + + // Clear password after successful encryption (security best practice) + setBackupMessagePassword(''); + setPrivateKeyPassphrase(''); // Also clear PGP passphrase if used } catch (e) { setError(e instanceof Error ? e.message : 'Encryption failed'); } finally { @@ -402,6 +450,12 @@ function App() { // Temporarily display the mnemonic and then clear it setDecryptedRestoredMnemonic(result.w); + + // Clear passwords after successful decryption (security best practice) + setRestoreMessagePassword(''); + setPrivateKeyPassphrase(''); + // Also clear restore input after successful decrypt + setRestoreInput(''); setTimeout(() => { setDecryptedRestoredMnemonic(null); }, 10000); // Auto-clear after 10 seconds @@ -446,15 +500,37 @@ function App() { setShowLockConfirm(false); }; - const handleRequestTabChange = (newTab: 'backup' | 'restore' | 'seedblender') => { - if (activeTab === 'seedblender' && isBlenderDirty) { - if (window.confirm("You have unsaved data in the Seed Blender. Are you sure you want to leave? All progress will be lost.")) { - setActiveTab(newTab); - setIsBlenderDirty(false); // Reset dirty state on leaving - } - // else: user cancelled, do nothing - } else { - setActiveTab(newTab); + const handleRequestTabChange = (newTab: 'create' | 'backup' | 'restore' | 'seedblender') => { + // Allow free navigation - no warnings + // User can manually reset Seed Blender with "Reset All" button + setActiveTab(newTab); + }; + + const handleResetAll = () => { + if (window.confirm("Reset entire app? This will clear all seeds, passwords, and generated data.")) { + // Clear all state + setMnemonic(''); + setGeneratedSeed(''); + setBackupMessagePassword(''); + setRestoreMessagePassword(''); + setPublicKeyInput(''); + setPrivateKeyInput(''); + setPrivateKeyPassphrase(''); + setQrPayload(''); + setRecipientFpr(''); + setRestoreInput(''); + setDecryptedRestoredMnemonic(null); + setError(''); + setSeedForBlender(''); + setIsBlenderDirty(false); + // Clear session + destroySessionKey(); + setEncryptedMnemonicCache(null); + // Go to Create tab (fresh start) + // Force SeedBlender to remount (resets its internal state) + setBlenderResetKey(prev => prev + 1); + + setActiveTab('create'); } }; @@ -509,6 +585,7 @@ function App() { appVersion={__APP_VERSION__} isLocked={isReadOnly} onToggleLock={handleToggleLock} + onResetAll={handleResetAll} />
@@ -540,136 +617,274 @@ function App() { {/* Main Content Grid */}
- {activeTab === 'backup' ? ( - <> -
- -