Skip to content

Conversation

@bordalix
Copy link
Collaborator

@bordalix bordalix commented Nov 26, 2025

Refactors backup of private key as per new design.

@tiero please review

Summary by CodeRabbit

  • New Features

    • Modal-driven private-key reveal with biometric or password verification and copy-to-clipboard.
  • New Icons

    • Added several new status and safety icons used across dialogs and warnings.
  • UI/UX

    • Masked/reveal private-key controls, safer reveal flow, updated Settings label to "backup", and a read-only input-like display for keys.
  • Style

    • Slightly wider center container, adjusted shadow fallback color, button spacing tweak, and updated orange color tokens.
  • Chores

    • Type-safety and internal typing refinements across many components.
  • Tests

    • E2E tests updated to use test-id tab navigation and the new private-key reveal flow.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

Walkthrough

Replaces the Settings → Backup private-key flow with a modal-driven reveal requiring biometric or password verification; adds Modal and InputFake components, four new SVG icons, many style typings, small UI styling tweaks, testId additions, and e2e test updates.

Changes

Cohort / File(s) Summary
Backup screen refactor
src/screens/Settings/Backup.tsx
Replaces prior password-only flow with a dialog/modal-based private-key reveal flow that supports biometric or password verification, manages dialog/key-reveal/copy state, and integrates new UI pieces and icons.
New icon components
src/icons/Dont.tsx, src/icons/Safe.tsx, src/icons/X.tsx, src/icons/Ok.tsx
Added four new default-export React SVG icon components (16×16, use currentColor, with opacity).
New presentational components
src/components/Modal.tsx, src/components/InputFake.tsx
Added a full-viewport centered Modal component and an InputFake static-styled text container component.
Style & small UI tweaks
src/components/CenterScreen.tsx, src/components/Shadow.tsx, src/components/Button.tsx, src/components/Warning.tsx, src/ionic.css
CenterScreen max-width increased (16rem → 18rem); Shadow fallback background updated to var(--dark10); Button adds 4px vertical margin; Warning icon, padding, border and color adjusted; CSS color tokens updated for orange and orangebg.
Type refinements (many components)
src/components/AlertBox.tsx, src/components/Bullet.tsx, src/components/Checkbox.tsx, src/components/FlexRow.tsx, src/components/Padded.tsx, src/components/StepBars.tsx, src/components/Strength.tsx, src/components/Title.tsx, src/icons/Logo.tsx, src/icons/Megaphone.tsx, src/screens/Apps/Index.tsx, src/screens/Settings/Vtxos.tsx, src/components/Shadow.tsx, src/components/CenterScreen.tsx
Added or tightened explicit React.CSSProperties type annotations for local style objects and internal style function signatures; no runtime behavior changes.
FlexCol test ids & tab test hooks
src/components/FlexCol.tsx, src/App.tsx
FlexCol accepts optional testId?: string and renders data-testid; three tab items now supply test IDs (tab-wallet, tab-apps, tab-settings).
Types update
src/lib/types.ts
Changed SettingsOptions.Backup enum value from 'backup and privacy' to 'backup'.
Tests updated
src/test/e2e/nostr.test.ts, src/test/e2e/send.test.ts, src/test/e2e/swap.test.ts
Replaced text-based tab selectors with test-id selectors, updated backup e2e flow to open/view private key via dialog and read key from private-key test ID; adjusted navigation selectors and small waits.
Minor component prop/style tweaks
src/components/FlexCol.tsx, src/components/Button.tsx, src/components/Header.tsx, src/components/CenterScreen.tsx
Added optional testId prop, small inline style changes, and replaced DOM placeholder elements with literal non‑breaking space characters in Header.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant User
participant BackupUI as Backup.tsx
participant ModalComp as Modal
participant Biometrics as WalletContext (Biometrics)
participant Wallet as WalletContext / Crypto
User->>BackupUI: click "View private key"
BackupUI->>ModalComp: open dialog (password or biometric option)
alt Biometrics available & chosen
ModalComp->>Biometrics: request authentication
Biometrics-->>ModalComp: success (token)
ModalComp->>Wallet: derive/verify nsec using token
else Password entered
User->>ModalComp: submit password
ModalComp->>Wallet: verify password, derive nsec
end
Wallet-->>ModalComp: return nsec or error
ModalComp->>BackupUI: reveal nsec or show error
BackupUI->>User: display masked/revealed key; user may copy or close

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Pay special attention to src/screens/Settings/Backup.tsx (auth flow, secure nsec handling, state transitions).
  • Review WalletContext biometric/auth integration and secret derivation paths.
  • Validate e2e test updates (src/test/e2e/nostr.test.ts, send.test.ts, swap.test.ts) align with testId changes and enum rename.
  • Check new SVG components for correct exports and consistent attributes.

Possibly related PRs

Suggested reviewers

  • tiero

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor nsec backup' directly and clearly describes the main change: a refactoring of the nsec (private key) backup functionality, which is the primary focus of the changeset across multiple files.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor_nsec_backup

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91a4a4e and 5572692.

📒 Files selected for processing (1)
  • src/ionic.css (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: Cloudflare Pages: wallet-bitcoin
🔇 Additional comments (1)
src/ionic.css (1)

16-17: Approve light-theme orange color updates; verify dark-theme consistency.

The light-theme color updates align with the PR's design goals:

  • Stronger orange (#ff8e24) for text emphasis on warnings
  • Transparent orange background (#ff8e2418 with ~24% opacity)

However, the dark-theme palette (lines 43–44) retains the previous orange values (#e69b39 and #2e1800). Please verify whether the dark-theme orange values should also be updated to maintain consistent warning/emphasis styling across both themes.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/NeedsPassword.tsx (1)

32-37: Avoid double-triggering biometrics on click

With onClick={handleBiometrics} on CenterScreen and also on the Button, clicking the button will call authenticateUser twice (once via the button, once bubbling to the parent div). This can cause duplicated biometric prompts or odd UX.

A simple fix is to let the container handle the click and keep the button as visual only:

-            <CenterScreen onClick={handleBiometrics}>
+            <CenterScreen onClick={handleBiometrics}>
               <LockIcon big />
               <Text centered>Unlock with your passkey</Text>
-              <Button onClick={handleBiometrics} label='Unlock wallet' />
+              <Button label='Unlock wallet' />
🧹 Nitpick comments (3)
src/screens/Settings/Backup.tsx (3)

32-50: Clear authentication error on successful unlock

Right now setError('Invalid password') is only called in the catch, and there’s no corresponding reset on success. If the user enters a wrong password once and then a correct one, the old “Invalid password” message will still be present when they return to the main view.

You can clear the error when getPrivateKey succeeds:

  useEffect(() => {
    const pass = password ? password : defaultPassword
    getPrivateKey(pass)
-      .then((privateKey) => {
-        setNsec(privateKeyToNsec(privateKey))
-      })
+      .then((privateKey) => {
+        setError('')
+        setNsec(privateKeyToNsec(privateKey))
+      })

If you want to keep backup-related errors independent of password errors, consider splitting them into separate state variables as a follow-up refactor.


83-118: Dialog’s inline password prompt is effectively dead code

Because the parent only renders <Dialog /> when dialog is true and nsec is truthy, the !nsec conditional inside Dialog:

{!nsec ? <p>Enter your password</p> : null}
{!nsec ? <input type='password' onChange={(e) => setPassword(e.target.value)} /> : null}

will never be hit. The password flow is now handled by NeedsPassword.

To reduce confusion and maintenance overhead, consider removing this unused branch entirely:

-          {!nsec ? <p>Enter your password</p> : null}
-          {!nsec ? <input type='password' onChange={(e) => setPassword(e.target.value)} /> : null}

120-161: Private key reveal & copy flow is well-gated; consider optional hardening

The new flow—masking the key by default, requiring a dialog + explicit confirm to set showNsec, and only wiring handleCopy to the “Copy to clipboard” button in the showNsec branch—appropriately gates access to the private key and matches the intent of keeping it hidden by default. Based on learnings, this is consistent with the pattern of only exposing sensitive material after explicit user action.

If this screen can remain mounted while the user navigates elsewhere, you might optionally clear nsec/showNsec when leaving the page (e.g., in an effect cleanup or on route change) so the key isn’t immediately visible when returning, mirroring the non-persistence approach used for the restore input.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53d36d0 and 6688255.

📒 Files selected for processing (7)
  • src/components/CenterScreen.tsx (1 hunks)
  • src/components/NeedsPassword.tsx (3 hunks)
  • src/components/Shadow.tsx (1 hunks)
  • src/icons/Dont.tsx (1 hunks)
  • src/icons/Safe.tsx (1 hunks)
  • src/icons/X.tsx (1 hunks)
  • src/screens/Settings/Backup.tsx (5 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.
📚 Learning: 2025-08-06T09:53:28.428Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.

Applied to files:

  • src/screens/Settings/Backup.tsx
  • src/components/NeedsPassword.tsx
📚 Learning: 2025-07-21T18:25:36.391Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.

Applied to files:

  • src/screens/Settings/Backup.tsx
  • src/components/NeedsPassword.tsx
🧬 Code graph analysis (2)
src/screens/Settings/Backup.tsx (9)
src/components/CenterScreen.tsx (1)
  • CenterScreen (9-28)
src/components/Shadow.tsx (1)
  • Shadow (18-55)
src/components/FlexCol.tsx (1)
  • FlexCol (15-40)
src/components/Text.tsx (1)
  • Text (25-79)
src/components/FlexRow.tsx (1)
  • FlexRow (18-50)
src/components/Button.tsx (1)
  • Button (21-63)
src/components/Header.tsx (1)
  • Header (17-53)
src/components/NeedsPassword.tsx (1)
  • NeedsPassword (20-55)
src/components/Warning.tsx (1)
  • WarningBox (12-38)
src/components/NeedsPassword.tsx (1)
src/components/Button.tsx (1)
  • Button (21-63)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: Cloudflare Pages: wallet-bitcoin
🔇 Additional comments (6)
src/components/CenterScreen.tsx (1)

19-19: Max-width increase is fine

Bumping maxWidth to 18rem is a safe layout tweak and doesn’t affect behavior.

src/components/Shadow.tsx (1)

33-42: Background fallback change is purely visual

Switching the default background to var(--dark10) only affects theming; no behavioral impact.

src/icons/X.tsx (1)

1-11: XIcon component looks good

Self-contained SVG icon with currentColor fill is fine and consistent with typical icon patterns.

src/icons/Safe.tsx (1)

1-11: SafeIcon SVG is straightforward and reusable

Stateless SVG icon with currentColor fill is correctly implemented.

src/icons/Dont.tsx (1)

1-18: DontIcon SVG implementation is fine

Icon structure (clipPath + single path) is valid and consistent with the other new icons.

src/components/NeedsPassword.tsx (1)

39-49: Password unlock flow wiring looks solid

Inline password input, error display via ErrorMessage, and the explicit “Unlock wallet” button correctly route through onPassword(password) and align with the existing parent handling.

@tiero
Copy link
Member

tiero commented Nov 26, 2025

Just Backup (no "and privacy" in "Backup and Privacy" in Settings

@tiero
Copy link
Member

tiero commented Nov 26, 2025

the impl is not really 1:1 with the design

the confirmation modal should just overlay (not change the background), and def be bigger as per design.
image
image

@tiero
Copy link
Member

tiero commented Nov 26, 2025

also the warnign notification box should be transprent orange and stronger orange I think on the text

image image

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Nov 27, 2025

Deploying wallet-bitcoin with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5572692
Status: ✅  Deploy successful!
Preview URL: https://7c5a1c1e.wallet-bitcoin.pages.dev
Branch Preview URL: https://refactor-nsec-backup.wallet-bitcoin.pages.dev

View logs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/Warning.tsx (1)

2-35: Remove unused InfoIcon import and align text color with variant

  • InfoIcon is imported but never used; this triggers the static‑analysis warning and should be removed.
  • The warning text color is hard‑coded to orange via color: 'var(--orange)', even when red or green are set. That likely makes red/green variants visually inconsistent with their border/icon color.

A minimal refactor would reuse the per‑variant color for text:

-import InfoIcon, { InfoIconDark } from '../icons/Info'
+import { InfoIconDark } from '../icons/Info'

 export default function WarningBox({ green, red, text }: WarningProps) {
   const backgroundColor = red ? 'var(--redbg)' : green ? 'var(--greenbg)' : 'var(--orangebg)'
   const borderColor = red ? 'var(--red)' : green ? 'var(--green)' : 'var(--orange)'
-  const Icon = () => (red ? <ForbidIcon /> : <InfoIconDark />)
+  const Icon = () => (red ? <ForbidIcon /> : <InfoIconDark />)

-  const style = {
+  const style = {
     backgroundColor,
     borderRadius: '0.5rem',
-    color: 'var(--orange)',
+    color: borderColor,
     padding: '0.75rem 1rem',
     width: '100%',
   }

This keeps the new orange styling for the default case while making red/green variants consistent with their semantic color.

🧹 Nitpick comments (7)
src/icons/Ok.tsx (1)

1-10: LGTM! Clean icon implementation.

The component follows a clean pattern with inline SVG and currentColor fill, making it adaptable to different text color contexts. The fixed 16×16 size aligns with the other icons in this set.

Optionally, consider adding accessibility attributes (e.g., aria-hidden="true" if decorative, or role="img" with aria-label if semantic) depending on how this icon is used in the UI.

src/components/Button.tsx (1)

36-43: Inline margin change is fine; consider CSS class if spacing grows

The added style={{ margin: '4px 0' }} cleanly enforces vertical spacing and is safe. If more button-specific spacing rules accumulate, consider moving this to a shared CSS class for easier theming and consistency across variants.

src/components/InputFake.tsx (1)

1-20: InputFake is a good fit for read‑only secret display; wrap prop is likely redundant

This component cleanly encapsulates the “fake input” styling for displaying (or masking) the nsec and keeps the actual password input uncontrolled elsewhere, which aligns with your prior secret‑handling approach. Based on TextSecondary’s implementation, the explicit wrap prop here is probably redundant, since TextSecondary always enables wrapping internally—you could drop it for a slightly cleaner call site if you like:

-      <TextSecondary wrap>{text}</TextSecondary>
+      <TextSecondary>{text}</TextSecondary>

Based on learnings, this keeps the displayed secret in a dedicated read‑only component while the actual password remains outside controlled React state.

src/components/Modal.tsx (1)

1-31: Modal overlay positioning and opacity could better match the intended dialog design

The current overlay works but has a few UX quirks:

  • Using position: 'absolute' ties the overlay to the nearest positioned ancestor; on some layouts it may not fully cover the viewport or header. fixed is usually safer for full‑screen modals.
  • Applying opacity: 0.95 to the outer wrapper also fades the dialog content, not just the backdrop. A semi‑transparent backdrop color typically gives better contrast while keeping the dialog itself fully opaque.
  • Design feedback mentions a prominent confirmation modal; maxWidth: 'min(22rem, 90%)' might be on the small side depending on typography.

Consider something along these lines:

-  const outerStyle = {
-    top: '0',
-    left: '0',
-    zIndex: 21,
-    opacity: 0.95,
-    width: '100%',
-    height: '100%',
-    display: 'flex',
-    alignItems: 'center',
-    justifyContent: 'center',
-    position: 'absolute' as 'absolute',
-    backgroundColor: 'var(--ion-background-color)',
-  }
+  const outerStyle = {
+    top: 0,
+    left: 0,
+    zIndex: 21,
+    width: '100%',
+    height: '100%',
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+    position: 'fixed' as const,
+    backgroundColor: 'rgba(0, 0, 0, 0.4)', // or a dedicated backdrop token
+  }
   const innerStyle = {
-    zIndex: 21,
-    opacity: 1,
+    zIndex: 22,
     padding: '1rem',
-    maxWidth: 'min(22rem, 90%)',
+    maxWidth: 'min(26rem, 90%)',
     borderRadius: '0.5rem',
     border: '1px solid rgba(251, 251, 251, 0.10)',
     backgroundColor: 'var(--ion-background-color)',
   }

This should better overlay the existing screen without visually “flattening” the dialog itself and gives you a bit more room to match the design comps.

src/screens/Settings/Backup.tsx (3)

33-36: Share one error state across backup failures and password errors may confuse messaging

The single error state is now used for:

  • Nostr backup failures in toggleNostrBackup ('Backup to Nostr failed'), and
  • Password validation inside the dialog ('Invalid password').

Because the dialog also renders <ErrorMessage error={Boolean(error)} text={error} />, a backup error will appear inside the password dialog if the user opens it later, and a password error will persist in the main page’s top‑level ErrorMessage after closing the dialog.

Consider splitting this into two separate pieces of state, e.g.:

-  const [error, setError] = useState('')
+  const [backupError, setBackupError] = useState('')
+  const [passwordError, setPasswordError] = useState('')

 // in toggleNostrBackup
-        setError('Backup to Nostr failed')
+        setBackupError('Backup to Nostr failed')

 // in Dialog
-          <ErrorMessage error={Boolean(error)} text={error} />
+          <ErrorMessage error={Boolean(passwordError)} text={passwordError} />

 // top-level
-            <ErrorMessage error={Boolean(error)} text={error} />
+            <ErrorMessage error={Boolean(backupError)} text={backupError} />

This keeps error messages scoped to their respective flows and avoids leaking unrelated errors into the modal.

Also applies to: 74-88, 90-107


59-68: Clarify showPrivateKey flow and add a simple empty‑password guard

Functionally, showPrivateKey works, but a couple of small tweaks would make it clearer and more robust:

  • verifyPassword returns an nsec string, but the local variable is named privateKey, which is misleading.
  • When nsec is empty, input.current?.value could be undefined or an empty string; you currently just pass it through and show “Invalid password” on failure.

You could address both with:

-  const showPrivateKey = async () => {
-    if (!nsec) {
-      const privateKey = await verifyPassword(input.current?.value as string)
-      setError(privateKey ? '' : 'Invalid password')
-      if (!privateKey) return
-      setNsec(privateKey)
-    }
+  const showPrivateKey = async () => {
+    if (!nsec) {
+      const password = (input.current?.value || '') as string
+      if (!password.trim()) {
+        setPasswordError('Enter your password')
+        return
+      }
+
+      const nsecFromPassword = await verifyPassword(password)
+      setPasswordError(nsecFromPassword ? '' : 'Invalid password')
+      if (!nsecFromPassword) return
+      setNsec(nsecFromPassword)
+    }
     setShowNsec(true)
     setDialog(false)
   }

(Adjust setPasswordError vs setError based on whether you adopt the split‑error suggestion above.)

This keeps naming aligned with what’s actually stored and gives users a clearer message when they hit “Confirm” with an empty password.

Also applies to: 90-125


33-39: Confirm that eagerly deriving nsec with defaultPassword matches your security model

On mount you derive and store the nsec with:

useEffect(() => {
  verifyPassword(defaultPassword).then(setNsec)
}, [])

and later:

  • Skip the password prompt in the dialog whenever nsec is already non‑empty.
  • Keep nsec in React state, using InputFake to mask or show it based on showNsec.

Given your previous choice to keep sensitive inputs uncontrolled, this is a slightly stronger exposure: the secret is always in component state once the screen mounts (for the default‑password scenario), even before the user requests to view it, and subsequent reveals do not require re‑authentication.

If the product decision is that “default password” sessions don’t need additional verification, this is fine. Otherwise, you might want to:

  • Only call verifyPassword from showPrivateKey, and
  • Avoid caching nsec longer than necessary (e.g., clear it when hiding the key or leaving the screen).

Just worth double‑checking this against your intended threat model for backups.

Also applies to: 90-99, 141-160

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6688255 and 80ae144.

📒 Files selected for processing (7)
  • src/components/Button.tsx (1 hunks)
  • src/components/InputFake.tsx (1 hunks)
  • src/components/Modal.tsx (1 hunks)
  • src/components/Warning.tsx (2 hunks)
  • src/icons/Ok.tsx (1 hunks)
  • src/lib/types.ts (1 hunks)
  • src/screens/Settings/Backup.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-21T18:25:36.391Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.

Applied to files:

  • src/components/InputFake.tsx
  • src/screens/Settings/Backup.tsx
📚 Learning: 2025-08-06T09:53:28.428Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.

Applied to files:

  • src/screens/Settings/Backup.tsx
🧬 Code graph analysis (2)
src/components/InputFake.tsx (1)
src/components/Text.tsx (1)
  • TextSecondary (91-97)
src/components/Warning.tsx (3)
src/icons/Forbid.tsx (1)
  • ForbidIcon (1-29)
src/icons/Info.tsx (1)
  • InfoIconDark (16-27)
src/components/FlexRow.tsx (1)
  • FlexRow (18-50)
🪛 GitHub Check: test
src/components/Warning.tsx

[warning] 2-2:
'InfoIcon' is defined but never used


[warning] 2-2:
'InfoIcon' is defined but never used

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: Cloudflare Pages: wallet-bitcoin
🔇 Additional comments (2)
src/screens/Settings/Backup.tsx (1)

40-51: verifyPassword is used before initialization—this will throw at runtime

useEffect calls verifyPassword(defaultPassword) before verifyPassword is defined as a const arrow function. Because const bindings are not hoisted, this hits the temporal dead zone and will cause a runtime error the first time this component renders.

You can fix this either by moving verifyPassword above the useEffect, or by converting it to a function declaration. For example:

-  useEffect(() => {
-    verifyPassword(defaultPassword).then(setNsec)
-  }, [])
-
-  const verifyPassword = async (password: string): Promise<string> => {
+  const verifyPassword = async (password: string): Promise<string> => {
     try {
       const privateKey = await getPrivateKey(password)
       return privateKeyToNsec(privateKey)
     } catch {
       return ''
     }
   }
+
+  useEffect(() => {
+    verifyPassword(defaultPassword).then(setNsec)
+  }, [])

After this change, the effect can safely invoke verifyPassword on mount.

⛔ Skipped due to learnings
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.
src/lib/types.ts (1)

46-64: Based on my comprehensive verification of the codebase:

The enum value change for SettingsOptions.Backup is safe—no persistence or external key usage detected.

The search results confirm:

  • The old string 'backup and privacy' does not exist anywhere in the codebase
  • SettingsOptions.Backup is used exclusively for in-memory UI state management: component routing in Settings/Index.tsx and navigation context calls
  • No localStorage, sessionStorage, or config persistence of these enum values was found
  • The enum is not used as a route identifier or serialized configuration key

The enum value serves only as a UI option identifier for conditional component rendering. The concern in the original review comment about breaking lookups or migrations is not applicable here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/screens/Settings/Backup.tsx (1)

97-140: Separate backup vs auth error state to avoid confusing messages in the dialog

The dialog/layout with Modal, WarningBox, and the copy/view flow looks good and matches the described UX. One minor UX concern: the same error state is used for both private‑key auth issues (invalid password / biometric failure) and Nostr backup failures, but it’s rendered both inside the dialog (<ErrorMessage> under the password input) and at the top of the screen. That means a “Backup to Nostr failed” message can show up under the password field the next time the dialog opens.

Consider splitting this into two states, e.g. authError and backupError, and wiring:

  • Dialog’s ErrorMessage to authError.
  • Top‑level ErrorMessage to backupError (or to backupError || authError if you still want a global banner).

That keeps concerns separated and avoids unrelated errors bleeding into the password prompt.

Also applies to: 145-174, 175-181

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80ae144 and 8183232.

📒 Files selected for processing (1)
  • src/screens/Settings/Backup.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.
📚 Learning: 2025-08-06T09:53:28.428Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.

Applied to files:

  • src/screens/Settings/Backup.tsx
📚 Learning: 2025-07-21T18:25:36.391Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.

Applied to files:

  • src/screens/Settings/Backup.tsx
📚 Learning: 2025-06-30T18:33:29.839Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 114
File: src/lib/asp.ts:0-0
Timestamp: 2025-06-30T18:33:29.839Z
Learning: In src/lib/asp.ts, only the collaborativeExit function should accept both IWallet and ServiceWorkerWallet types. Other wallet functions (getBalance, getTxHistory, getReceivingAddresses, redeemNotes, sendOnChain, settleVtxos) should maintain their original IWallet-only signatures and not be updated for consistency.

Applied to files:

  • src/screens/Settings/Backup.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: Cloudflare Pages: wallet-bitcoin
🔇 Additional comments (1)
src/screens/Settings/Backup.tsx (1)

1-2: Init + verification logic looks solid; confirm default‑password behavior

Nice job keeping verifyPassword isolated, returning only nsec and never persisting the raw private key. The hook wiring (including the useRef for the password input) looks clean.

One thing to double‑check: calling verifyPassword(defaultPassword).then(setNsec) on mount means that wallets still using the default password will only need to click “View private key” → “Confirm”, without entering a password/biometric again. If that shortcut is intentional, this is fine; if you expect a hard auth step for all users, you may want to gate the default‑password resolution behind an explicit check/flag instead of always running it on mount.

Also applies to: 19-29, 32-32, 39-42, 44-55

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8183232 and 46b99ed.

📒 Files selected for processing (2)
  • src/components/Warning.tsx (2 hunks)
  • src/test/e2e/nostr.test.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Warning.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cloudflare Pages: wallet-bitcoin
  • GitHub Check: test
🔇 Additional comments (1)
src/test/e2e/nostr.test.ts (1)

34-34: LGTM! Selector updated to match renamed settings option.

The change from a broader label match to an exact text match on "backup" correctly reflects the UI rename from "Backup and Privacy" to "Backup" as noted in the PR comments.

Also applies to: 51-51, 67-67

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/screens/Settings/Backup.tsx (1)

63-75: Critical: Invalid passwords still close the dialog and transition to "revealed" state.

This is the same issue flagged in previous reviews. When verifyPassword returns an empty string (invalid password), lines 73-74 still execute, closing the dialog and setting showNsec=true. This shows the "revealed" UI state (with warnings) even though the private key is empty.

Additionally, line 66's .catch(setError) passes an Error object to setError, but error is typed as string.

Apply this diff to fix both issues:

   const showPrivateKey = async () => {
     if (!nsec) {
       const password = wallet.lockedByBiometrics
-        ? await authenticateUser(wallet.passkeyId).catch(setError)
+        ? await authenticateUser(wallet.passkeyId).catch((err) => {
+            setError('Authentication failed')
+            return ''
+          })
         : (input.current?.value as string)
+
       if (!password) return
+
       const privateKey = await verifyPassword(password)
-      setError(privateKey ? '' : 'Invalid password')
-      setNsec(privateKey ?? '')
+      if (!privateKey) {
+        setError('Invalid password')
+        return
+      }
+
+      setError('')
+      setNsec(privateKey)
     }
+
     setShowNsec(true)
     setDialog(false)
   }

This keeps the dialog open with a clear error message on authentication failure, and only transitions to the revealed state when a valid private key is obtained.

🧹 Nitpick comments (2)
src/components/Header.tsx (1)

43-43: Verify header layout after removing <p> elements.

Replacing <p>&nbsp;</p> with a text node ('\u00A0') removes the paragraph element's default margins from the DOM. While this simplifies the structure, the <p> element's box model may have contributed to vertical alignment or spacing of the header elements.

Please verify that:

  • The header's vertical alignment (back button, title, aux actions) remains consistent
  • No visual regressions occur, especially on different screen sizes

Also applies to: 48-48

src/components/Bullet.tsx (1)

10-10: Remove redundant type cast.

The as React.CSSProperties cast on line 10 is redundant since the style variable is already explicitly typed as React.CSSProperties on line 2.

Apply this diff to remove the redundant cast:

-  } as React.CSSProperties
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46b99ed and b24c7d7.

📒 Files selected for processing (18)
  • src/components/AlertBox.tsx (1 hunks)
  • src/components/Bullet.tsx (1 hunks)
  • src/components/Checkbox.tsx (1 hunks)
  • src/components/FlexRow.tsx (1 hunks)
  • src/components/Header.tsx (1 hunks)
  • src/components/InputFake.tsx (1 hunks)
  • src/components/Padded.tsx (1 hunks)
  • src/components/Shadow.tsx (2 hunks)
  • src/components/StepBars.tsx (1 hunks)
  • src/components/Strength.tsx (1 hunks)
  • src/components/Title.tsx (1 hunks)
  • src/components/Warning.tsx (2 hunks)
  • src/icons/Logo.tsx (1 hunks)
  • src/icons/Megaphone.tsx (1 hunks)
  • src/screens/Apps/Index.tsx (2 hunks)
  • src/screens/Settings/Backup.tsx (3 hunks)
  • src/screens/Settings/Vtxos.tsx (2 hunks)
  • src/test/e2e/nostr.test.ts (4 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/components/Padded.tsx
  • src/icons/Logo.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/Shadow.tsx
  • src/components/InputFake.tsx
  • src/components/Warning.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.
📚 Learning: 2025-08-06T09:53:28.428Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.

Applied to files:

  • src/screens/Settings/Backup.tsx
📚 Learning: 2025-07-21T18:25:36.391Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.

Applied to files:

  • src/screens/Settings/Backup.tsx
🧬 Code graph analysis (1)
src/screens/Settings/Backup.tsx (7)
src/providers/wallet.tsx (1)
  • WalletContext (42-56)
src/lib/privateKey.ts (2)
  • getPrivateKey (40-44)
  • privateKeyToNsec (30-33)
src/lib/biometrics.ts (1)
  • authenticateUser (71-98)
src/components/Text.tsx (2)
  • Text (25-79)
  • TextSecondary (91-97)
src/components/Modal.tsx (1)
  • Modal (1-31)
src/components/Shadow.tsx (1)
  • Shadow (18-55)
src/components/InputFake.tsx (1)
  • InputFake (3-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: Cloudflare Pages: wallet-bitcoin
🔇 Additional comments (13)
src/components/Title.tsx (1)

6-6: LGTM! Good type safety improvement.

The explicit React.CSSProperties type annotation improves type safety and aligns with the broader typing refinements across the PR.

src/components/AlertBox.tsx (1)

59-59: LGTM!

The explicit type annotation improves type safety without affecting runtime behavior.

src/icons/Megaphone.tsx (1)

6-6: LGTM!

The explicit type annotation improves type safety without affecting runtime behavior.

src/components/Checkbox.tsx (1)

10-10: LGTM!

The explicit type annotation improves type safety without affecting runtime behavior.

src/components/FlexRow.tsx (1)

33-33: LGTM!

The explicit type annotation improves type safety without affecting runtime behavior.

src/screens/Settings/Vtxos.tsx (1)

137-137: LGTM!

The explicit type annotations in both Box and CoinLine improve type safety without affecting runtime behavior.

Also applies to: 180-180

src/components/Bullet.tsx (1)

2-2: LGTM!

The explicit type annotation improves type safety.

src/components/StepBars.tsx (1)

6-6: LGTM!

The explicit return type annotation improves type safety for the style function without affecting runtime behavior.

src/components/Strength.tsx (1)

36-36: LGTM!

The explicit return type annotation improves type safety for the style function without affecting runtime behavior.

src/screens/Apps/Index.tsx (1)

22-22: LGTM! Type safety improvements.

Adding explicit React.CSSProperties type annotations improves type safety and maintains consistency with the broader typing refinement pattern across the codebase.

Also applies to: 58-58

src/test/e2e/nostr.test.ts (3)

34-34: LGTM! Consistent navigation pattern.

Using exact text match 'backup', { exact: true } aligns with the UI changes and provides more reliable test navigation.

Also applies to: 51-51, 67-67, 188-188, 193-193


68-70: LGTM! Robust element selection.

The modal flow steps ("View private key" → "Confirm") followed by getByTestId('private-key') is significantly more robust than the previous nth() indexing approach and correctly reflects the new UI flow.


194-196: LGTM! Consistent test pattern.

The second test now correctly implements the same modal flow and element selection pattern as the first test, addressing the previous inconsistency.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/components/FlexCol.tsx (1)

41-45: Consider avoiding empty data-testid attributes.

When testId is not provided, data-testid="" is still rendered on the DOM element. This clutters the HTML and could cause selector issues if code accidentally matches on empty testId.

   return (
-    <div data-testid={testId ?? ''} style={style}>
+    <div data-testid={testId} style={style}>
       {children}
     </div>
   )

Alternatively, conditionally spread the attribute:

return (
  <div {...(testId && { 'data-testid': testId })} style={style}>
    {children}
  </div>
)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b24c7d7 and 91a4a4e.

📒 Files selected for processing (5)
  • src/App.tsx (1 hunks)
  • src/components/FlexCol.tsx (3 hunks)
  • src/test/e2e/nostr.test.ts (6 hunks)
  • src/test/e2e/send.test.ts (2 hunks)
  • src/test/e2e/swap.test.ts (6 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.
📚 Learning: 2025-06-30T18:33:29.839Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 114
File: src/lib/asp.ts:0-0
Timestamp: 2025-06-30T18:33:29.839Z
Learning: In src/lib/asp.ts, only the collaborativeExit function should accept both IWallet and ServiceWorkerWallet types. Other wallet functions (getBalance, getTxHistory, getReceivingAddresses, redeemNotes, sendOnChain, settleVtxos) should maintain their original IWallet-only signatures and not be updated for consistency.

Applied to files:

  • src/test/e2e/send.test.ts
  • src/test/e2e/nostr.test.ts
🧬 Code graph analysis (1)
src/App.tsx (1)
src/components/FlexCol.tsx (1)
  • FlexCol (16-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: Cloudflare Pages: wallet-bitcoin
🔇 Additional comments (6)
src/test/e2e/send.test.ts (1)

31-31: LGTM! TestId-based selectors improve test stability.

Using getByTestId('tab-wallet') is more resilient to UI text changes and internationalization compared to text-based selectors.

Also applies to: 62-62

src/App.tsx (1)

135-151: LGTM! TestId attributes enable robust e2e test selectors.

The testId values are consistently named and align with the selectors used across the e2e test suite.

src/test/e2e/swap.test.ts (1)

16-16: LGTM! Consistent migration to testId-based tab navigation.

All tab navigation selectors are uniformly updated to use getByTestId, improving test maintainability across all swap test scenarios.

Also applies to: 70-77, 112-112, 137-140, 175-175, 204-207

src/test/e2e/nostr.test.ts (3)

33-34: LGTM! Robust private key extraction using dedicated testId.

The migration from fragile nth() paragraph selectors to getByTestId('private-key') significantly improves test reliability. The consistent modal flow (View private keyConfirm) properly mirrors the new UI design.

Also applies to: 38-38, 45-45, 50-51, 55-55, 61-61, 66-70, 74-74, 93-93


109-109: LGTM! Consistent testId-based navigation throughout the test.

Tab navigation selectors are uniformly updated to use getByTestId.

Also applies to: 146-146, 152-152, 174-174, 177-177, 182-183


187-196: LGTM! Second test now aligns with the first test's modal flow.

The private key retrieval in the second test (should save swaps to nostr) now correctly uses the same modal confirmation flow and private-key testId as the first test, ensuring consistency.

Also applies to: 200-200, 219-219

@tiero
Copy link
Member

tiero commented Dec 1, 2025

LGTM. We may want to improve the copy but we can do after merge

@bordalix bordalix merged commit dc635da into master Dec 1, 2025
4 checks passed
@bordalix bordalix deleted the refactor_nsec_backup branch December 1, 2025 16:44
pietro909 pushed a commit to pietro909/wallet that referenced this pull request Dec 8, 2025
* refactor nsec backup

* wip

* new modal component

* wip

* authenticate with biometrics

* fix tests

* fix tests

* add testid to tabs

* more testid

* fix orange
This was referenced Dec 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants