Part of epic #40. Track: INP (interaction responsiveness). Paths are relative to carpincho-wallet/.
Priority
High
User story / Problem statement
Currently, password strength scoring blocks the main thread on every keystroke in the carpincho-wallet popup, and recurring background work (status polling, un-memoized handlers) competes with interactions while Home is mounted. This degrades the popup's INP on the common unlock/home path.
Expected outcome
- Password strength scoring no longer blocks keystroke repaint and is computed once per render rather than redundantly.
- Recurring main-thread work does not run while the popup is hidden and is cheaper while visible.
- Home-view re-renders are not defeated by handler closures recreated every render.
Acceptance criteria
Technical notes
- [High]
src/components/PasswordStrengthIndicator.tsx:23 — scorePassword(password) runs zxcvbn() synchronously inside render; the component re-renders on every keystroke because password is controlled state in NewPasswordFields (src/components/NewPasswordFields.tsx:59). zxcvbn is a known INP killer; every keypress blocks the main thread before the input repaints. The 1-entry memo (src/vault/passwordStrength.ts:34) only helps repeated identical strings. isNewPasswordPairValid (src/components/NewPasswordFields.tsx:39) calls scorePassword a second time per render, doubling the work. Fix: debounce scoring and compute once, pass down.
- [High / resolved-by-feedback]
src/vault/crypto.ts:4 — KDF_ITERATIONS = 600_000 PBKDF2-HMAC-SHA256 via crypto.subtle.deriveKey (src/vault/crypto.ts:35), invoked by unlock() on the unlock click. crypto.subtle is async and typically off the main thread in Chrome, so it does not fully block the event loop; 600k is a deliberate security trade-off. UnlockView sets busy synchronously and shows "Unlocking…" (src/views/UnlockView.tsx:74). No code change needed beyond existing feedback. Flag only if profiling shows the click handler itself janks.
- [Medium]
src/hooks/useWalletServiceStatus.ts:65 — a 5-second setInterval fires walletServiceRequest('status') continuously while Home is mounted, each tick re-rendering HomeView and below — recurring main-thread work competing with interactions. Fix: pause polling when the popup is hidden (document.visibilityState) and back off the interval.
- [Medium]
src/views/HomeView.tsx:148 — HomeView holds ~7 useState slices; usePendingActions (src/views/home/usePendingActions.ts:57) returns six handler closures recreated every render (no useCallback), spread into PendingActionsSection via {...pendingActions} (src/views/HomeView.tsx:172), defeating memoization. Cost is wasted re-renders. Fix: memoize the handler object / wrap in useCallback and React.memo the section.
- [Low]
src/vault/VaultContext.tsx:419 — idle auto-lock effect registers mousemove/keydown/click/scroll/touchstart; reset runs per event but is passive and storage writes are throttled to 5 s, so per-event cost is just clearTimeout/setTimeout. Noted for completeness; throttle is the right mitigation.
Additional context
Method: static (lab/heuristic) review — extension popups have no CrUX field data. Finding 7 is resolved by existing UX feedback and carries no code change.
Priority
High
User story / Problem statement
Currently, password strength scoring blocks the main thread on every keystroke in the carpincho-wallet popup, and recurring background work (status polling, un-memoized handlers) competes with interactions while Home is mounted. This degrades the popup's INP on the common unlock/home path.
Expected outcome
Acceptance criteria
useDeferredValueor a timer) so keystrokes paint immediately, and the score is computed once per render and passed down (no doublescorePasswordcall).useWalletServiceStatuspolling pauses when the popup is hidden (document.visibilityState) and backs off its interval.usePendingActionshandler closures are memoized (useCallback/ stable object) andPendingActionsSectionisReact.memo'd so spreading handlers no longer defeats memoization.Technical notes
src/components/PasswordStrengthIndicator.tsx:23—scorePassword(password)runszxcvbn()synchronously inside render; the component re-renders on every keystroke becausepasswordis controlled state inNewPasswordFields(src/components/NewPasswordFields.tsx:59). zxcvbn is a known INP killer; every keypress blocks the main thread before the input repaints. The 1-entry memo (src/vault/passwordStrength.ts:34) only helps repeated identical strings.isNewPasswordPairValid(src/components/NewPasswordFields.tsx:39) callsscorePassworda second time per render, doubling the work. Fix: debounce scoring and compute once, pass down.src/vault/crypto.ts:4—KDF_ITERATIONS = 600_000PBKDF2-HMAC-SHA256 viacrypto.subtle.deriveKey(src/vault/crypto.ts:35), invoked byunlock()on the unlock click.crypto.subtleis async and typically off the main thread in Chrome, so it does not fully block the event loop; 600k is a deliberate security trade-off.UnlockViewsets busy synchronously and shows "Unlocking…" (src/views/UnlockView.tsx:74). No code change needed beyond existing feedback. Flag only if profiling shows the click handler itself janks.src/hooks/useWalletServiceStatus.ts:65— a 5-secondsetIntervalfireswalletServiceRequest('status')continuously while Home is mounted, each tick re-renderingHomeViewand below — recurring main-thread work competing with interactions. Fix: pause polling when the popup is hidden (document.visibilityState) and back off the interval.src/views/HomeView.tsx:148—HomeViewholds ~7useStateslices;usePendingActions(src/views/home/usePendingActions.ts:57) returns six handler closures recreated every render (nouseCallback), spread intoPendingActionsSectionvia{...pendingActions}(src/views/HomeView.tsx:172), defeating memoization. Cost is wasted re-renders. Fix: memoize the handler object / wrap inuseCallbackandReact.memothe section.src/vault/VaultContext.tsx:419— idle auto-lock effect registers mousemove/keydown/click/scroll/touchstart; reset runs per event but is passive and storage writes are throttled to 5 s, so per-event cost is just clearTimeout/setTimeout. Noted for completeness; throttle is the right mitigation.Additional context
Method: static (lab/heuristic) review — extension popups have no CrUX field data. Finding 7 is resolved by existing UX feedback and carries no code change.