Skip to content

Improve carpincho-wallet popup INP: debounce password scoring and trim recurring work #62

@gabitoesmiapodo

Description

@gabitoesmiapodo

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

  1. [High] src/components/PasswordStrengthIndicator.tsx:23scorePassword(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.
  2. [High / resolved-by-feedback] src/vault/crypto.ts:4KDF_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.
  3. [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.
  4. [Medium] src/views/HomeView.tsx:148HomeView 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.
  5. [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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority: highMust be addressed in current sprint

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions