You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Sep 15, 2026. It is now read-only.
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
Password strength scoring is debounced (150–250 ms via useDeferredValue or a timer) so keystrokes paint immediately, and the score is computed once per render and passed down (no double scorePassword call).
useWalletServiceStatus polling pauses when the popup is hidden (document.visibilityState) and backs off its interval.
usePendingActions handler closures are memoized (useCallback / stable object) and PendingActionsSection is React.memo'd so spreading handlers no longer defeats memoization.
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.