From f52186f2e751783a5fc620dbe2f282485538920f Mon Sep 17 00:00:00 2001 From: LC mac Date: Tue, 10 Feb 2026 23:02:13 +0800 Subject: [PATCH] feat(entropy): Enhance entropy generation UX and fix resets This commit introduces several improvements to the entropy generation and application state management: 1. **Implement Dice Entropy Stats Panel:** - After generating entropy from dice rolls, a detailed statistics panel is now displayed for user review. - This panel includes roll distribution, chi-square analysis, and a preview of the generated seed. - Users can now choose to "Continue with this Seed" or "Roll Again" to discard and restart, improving user control and confidence in the entropy quality. 2. **Fix UI Layering and Overflow:** - Increased the header's `z-index` to `z-[100]` to ensure it always remains on top of other components, fixing an issue where the "Reset All" button was inaccessible. - Made the main content area for entropy components scrollable to prevent the new stats panels from overflowing the viewport on smaller screens. 3. **Improve "Reset All" Functionality:** - The "Reset All" button now correctly resets the internal state of the `DiceEntropy` and `CameraEntropy` components. - This is achieved by adding a `resetCounter` to the `App` state and passing it into the `key` prop of the entropy components, forcing a full remount on reset. --- src/App.tsx | 50 ++++++++++++--------- src/components/DiceEntropy.tsx | 80 ++++++++++++++++++++++++++++------ src/components/Header.tsx | 29 +++++------- 3 files changed, 108 insertions(+), 51 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d93a1bf..a00627b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -76,6 +76,7 @@ function App() { const [sessionItems, setSessionItems] = useState([]); const [clipboardEvents, setClipboardEvents] = useState([]); const [showLockConfirm, setShowLockConfirm] = useState(false); + const [resetCounter, setResetCounter] = useState(0); // Krux integration state const [encryptionMode, setEncryptionMode] = useState<'pgp' | 'krux' | 'seedqr'>('pgp'); @@ -467,17 +468,17 @@ function App() { const handleToggleNetwork = () => { setIsNetworkBlocked(!isNetworkBlocked); - + if (!isNetworkBlocked) { - // Block network - console.log('🚫 Network BLOCKED - No external requests allowed'); - // Optional: Override fetch/XMLHttpRequest - if (typeof window !== 'undefined') { - (window as any).__originalFetch = window.fetch; - // Create a mock fetch function with proper type assertion - const mockFetch = (async () => Promise.reject(new Error('Network blocked by user'))) as unknown as typeof window.fetch; - window.fetch = mockFetch; - } + // Block network + console.log('🚫 Network BLOCKED - No external requests allowed'); + // Optional: Override fetch/XMLHttpRequest + if (typeof window !== 'undefined') { + (window as any).__originalFetch = window.fetch; + // Create a mock fetch function with proper type assertion + const mockFetch = (async () => Promise.reject(new Error('Network blocked by user'))) as unknown as typeof window.fetch; + window.fetch = mockFetch; + } } else { // Unblock network console.log('🌐 Network ACTIVE'); @@ -494,8 +495,8 @@ function App() { }; const handleResetAll = () => { - if (window.confirm("Reset entire app? This will clear all seeds, passwords, and generated data.")) { - // Clear all state + if (window.confirm('⚠️ Reset ALL data? This will clear everything including any displayed entropy analysis.')) { + // Clear component state setMnemonic(''); setGeneratedSeed(''); setBackupMessagePassword(''); @@ -508,15 +509,23 @@ function App() { setRestoreInput(''); setDecryptedRestoredMnemonic(null); setError(''); + setEntropySource(null); + setEntropyStats(null); setSeedForBlender(''); - // Clear session + + // Clear storage and session + localStorage.clear(); + sessionStorage.clear(); destroySessionKey(); _setEncryptedMnemonicCache(null); - // Go to Create tab (fresh start) - // Force SeedBlender to remount (resets its internal state) + + // Force remount of key-driven components + setResetCounter(prev => prev + 1); setBlenderResetKey(prev => prev + 1); + // Go to Create tab (fresh start) setActiveTab('create'); + console.log('✅ All data reset'); } }; @@ -698,6 +707,7 @@ function App() { {/* Camera Entropy Component */} {entropySource === 'camera' && !generatedSeed && ( setEntropySource(null)} @@ -708,6 +718,7 @@ function App() { {/* Dice Entropy Component */} {entropySource === 'dice' && !generatedSeed && ( setEntropySource(null)} @@ -735,7 +746,7 @@ function App() {
-

e.target.classList.remove('blur-sensitive')} onBlur={(e) => mnemonic && e.target.classList.add('blur-sensitive')} placeholder="Enter your 12 or 24 word seed phrase..." - className={`w-full h-32 p-3 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg font-mono text-sm text-[#00f0ff] placeholder-[#9d84b7] focus:outline-none focus:border-[#ff006e] focus:shadow-[0_0_20px_rgba(255,0,110,0.5)] transition-all relative overflow-hidden ${ - mnemonic ? 'blur-sensitive' : '' - }`} + className={`w-full h-32 p-3 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg font-mono text-sm text-[#00f0ff] placeholder-[#9d84b7] focus:outline-none focus:border-[#ff006e] focus:shadow-[0_0_20px_rgba(255,0,110,0.5)] transition-all relative overflow-hidden ${mnemonic ? 'blur-sensitive' : '' + }`} style={{ backgroundImage: 'repeating-linear-gradient(0deg, rgba(0,240,255,0.03) 0px, transparent 1px, transparent 2px, rgba(0,240,255,0.03) 3px)', textShadow: '0 0 5px rgba(0,240,255,0.5)' @@ -1123,7 +1133,7 @@ function App() {

-

= ({ const [error, setError] = useState(''); const [processing, setProcessing] = useState(false); const [stats, setStats] = useState(null); + const [generatedMnemonic, setGeneratedMnemonic] = useState(''); const validateDiceRolls = (input: string): { valid: boolean; error: string } => { const clean = input.replace(/\s/g, ''); @@ -118,14 +119,11 @@ const DiceEntropy: React.FC = ({ // Generate mnemonic const mnemonic = await generateMnemonicFromDice(clean); - // Show stats first + // Show stats FIRST setStats(diceStats); + setGeneratedMnemonic(mnemonic); // Store mnemonic for later setProcessing(false); - - // Then notify parent after a brief delay so user sees stats - setTimeout(() => { - onEntropyGenerated(mnemonic, diceStats); - }, 100); + // DON'T call onEntropyGenerated yet - let user review stats first }; const generateMnemonicFromDice = async (diceRolls: string): Promise => { @@ -152,8 +150,9 @@ const DiceEntropy: React.FC = ({ }; return ( -

- {!stats && ( +
+ {/* INPUT FORM - Show only when stats are NOT shown */} + {!stats && !processing && ( <>

Dice Roll Entropy

@@ -168,7 +167,7 @@ const DiceEntropy: React.FC = ({
-