Skip to content

Conversation

@bordalix
Copy link
Collaborator

@bordalix bordalix commented Nov 28, 2025

Summary by CodeRabbit

  • New Features

    • Centralized, dismissible in-app nudges that surface on tabs, menu items, and inside Password settings.
    • Red-dot badges and pulsing indicators added to Wallet, Apps, and Settings icons and menu options.
  • UI

    • Alert visuals refined (smaller info font, clickable dismiss); wallet view simplified to show PSA messages only.
    • Deterministic test IDs added to address/transaction lines for more reliable UI testing.
  • Tests

    • New end-to-end nudge workflow test; updated Send screen expectation.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 28, 2025

Walkthrough

Introduces a centralized nudge system: NudgeProvider now exposes nudges/addNudge/addPasswordNudge/removeNudge; UI components and icons render red-dot nudges; NudgeProvider is moved outermost; wallet startup triggers password nudges; new NudgeComponent and AlertBox/AlertText exports; tests and testIDs added.

Changes

Cohort / File(s) Summary
App integration
src/App.tsx
Consumes NudgeContext; passes per-tab nudge to WalletIcon, AppsIcon, and SettingsIcon.
Provider & runtime
src/index.tsx, src/providers/nudge.tsx, src/providers/wallet.tsx
NudgeProvider moved to outermost wrapper; new in-memory nudge repo, Nudge type, nudges map, and APIs { addNudge, addPasswordNudge, removeNudge }; wallet startup effect calls addPasswordNudge() when balance threshold reached.
Icons (nudge-aware)
src/icons/Wallet.tsx, src/icons/Apps.tsx, src/icons/Settings.tsx
Icons accept optional nudge?: boolean prop and conditionally render a red badge; SettingsIcon/WalletIcon signatures updated.
Red dot icons
src/icons/RedDot.tsx
New file exporting default RedDotIcon and named RedDotIconAnimated (pulsing).
Menu & App UI
src/components/Menu.tsx, src/components/Nudge.tsx, src/components/AlertBox.tsx
Menu consumes NudgeContext and shows RedDotIcon beside options; new NudgeComponent (dismissible) added; AlertBox and AlertText exported, small style/font tweaks.
Screens
src/screens/Settings/Password.tsx, src/screens/Wallet/Index.tsx
Settings.Password consumes NudgeContext and renders NudgeComponent for password nudges; Wallet.Index removed local nudge usage and simplified PSA/nudge rendering.
Tests & testIDs
src/test/e2e/nudge.test.ts, src/test/screens/wallet/send.test.tsx, src/components/TransactionsList.tsx, src/components/ExpandAddresses.tsx
Added e2e nudge test; updated send test assertion; added data-testid='transactions-list'; ExpandLine gained optional testId and usages updated.

Sequence Diagram(s)

sequenceDiagram
    participant WalletProv as Wallet Provider
    participant NudgeProv as Nudge Provider
    participant App as App / UI
    participant User as User

    WalletProv->>WalletProv: initialize and compute balance
    alt balance ≥ minSatsToNudge
        WalletProv->>NudgeProv: addPasswordNudge()
        NudgeProv->>NudgeProv: add nudge to repo and rebuild nudges map
        NudgeProv-->>App: context value updates (nudges, APIs)
    end

    App->>App: consume NudgeContext\npass `nudge` props to icons/menu\nrender NudgeComponent where present

    User->>App: click dismiss on NudgeComponent
    App->>NudgeProv: removeNudge(nudge)
    NudgeProv->>NudgeProv: remove from repo and rebuild nudges map
    NudgeProv-->>App: context updates -> UI re-renders
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect src/providers/nudge.tsx for repo → nudges map correctness, deduplication, and remove semantics.
  • Verify src/providers/wallet.tsx startup effect calls addPasswordNudge() safely and handles async/race conditions.
  • Confirm consistent nudge prop signatures and usages across src/App.tsx, src/components/Menu.tsx, and icon files.
  • Check NudgeComponent dismissal wiring and that exported AlertBox/AlertText match call sites.

Possibly related PRs

  • Draft: V8 with ts3 #148 — modifies src/providers/nudge.tsx and NudgeContext/NudgeProvider implementation (strong code-level overlap).
  • Alerts are back #178 — overlaps AlertBox and nudge UI changes (AlertBox/AlertText exports and related UI behavior).
  • more unit tests #245 — changes to testId propagation in address components that relate to ExpandAddresses testId updates.

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 'new nudge (aka red dot) provider' accurately describes the main addition in this changeset: a new Nudge provider system that displays red dot indicators.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch nudge_provider

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.

@cloudflare-workers-and-pages
Copy link

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

Deploying wallet-bitcoin with  Cloudflare Pages  Cloudflare Pages

Latest commit: f34c1bb
Status: ✅  Deploy successful!
Preview URL: https://e7e1a616.wallet-bitcoin.pages.dev
Branch Preview URL: https://nudge-provider.wallet-bitcoin.pages.dev

View logs

@bordalix
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 28, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 (5)
src/icons/Apps.tsx (1)

1-11: Consider using theme variable for the nudge circle color

The nudge logic is fine, but here the circle uses a literal 'red' while RedDotIcon and other components use var(--red). For visual/theming consistency you might want to align this to fill='var(--red)'.

src/icons/Wallet.tsx (1)

1-13: Align nudge circle color with theme variables

Functionally this is fine, but the nudge dot uses a hard‑coded 'red'. For consistency with RedDotIcon and theming, consider switching to fill='var(--red)'.

src/index.tsx (1)

30-57: Provider reordering with NudgeProvider outermost looks safe

Wrapping the existing provider stack in NudgeProvider keeps their relative order and matches how NudgeContext is consumed in App and Menu; there’s no apparent dependency that would be broken by this move. Note that commenting out React.StrictMode does change dev‑time behaviour (no more double effects), so just make sure that’s intentional.

src/providers/nudge.tsx (2)

26-26: Consider more robust equality comparison.

The flat() function uses JSON.stringify for nudge equality, which is sensitive to property order. If object properties are serialized in different orders, identical nudges may not be recognized as equal.

Consider using a stable serialization approach or a dedicated equality function:

const flat = (nudge: Nudge) => {
  // Sort arrays to ensure consistent serialization
  return JSON.stringify({
    options: [...nudge.options].sort(),
    pages: [...nudge.pages].sort(),
    tabs: [...nudge.tabs].sort(),
    text: nudge.text,
  })
}

Or use a deep equality library for more reliable comparison.


38-59: Consider consolidating state updates for consistency.

Both addNudge and removeNudge call updateNudges before setRepo, which could theoretically lead to inconsistency if state updates don't complete synchronously. While React typically batches updates, using a single state update or functional updates would be more robust.

Example using a single state object:

const [state, setState] = useState<{ repo: Nudge[]; nudges: Record<string, Nudge[]> }>({
  repo: [],
  nudges: {},
})

const addNudge = (nudge: Nudge) => {
  setState(prev => {
    if (prev.repo.find((n) => flat(n) === flat(nudge))) return prev
    const newRepo = [...prev.repo, nudge]
    return { repo: newRepo, nudges: buildNudgesMap(newRepo) }
  })
}
📜 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 6626b18.

📒 Files selected for processing (14)
  • src/App.tsx (3 hunks)
  • src/components/AlertBox.tsx (5 hunks)
  • src/components/Menu.tsx (3 hunks)
  • src/components/Nudge.tsx (1 hunks)
  • src/icons/Apps.tsx (2 hunks)
  • src/icons/RedDot.tsx (1 hunks)
  • src/icons/Settings.tsx (2 hunks)
  • src/icons/Wallet.tsx (2 hunks)
  • src/index.tsx (1 hunks)
  • src/lib/constants.ts (1 hunks)
  • src/providers/nudge.tsx (1 hunks)
  • src/providers/wallet.tsx (3 hunks)
  • src/screens/Settings/Password.tsx (4 hunks)
  • src/screens/Wallet/Index.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 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/Wallet/Index.tsx
  • src/providers/wallet.tsx
📚 Learning: 2025-08-17T11:03:40.893Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 142
File: src/screens/Wallet/Unlock.tsx:18-27
Timestamp: 2025-08-17T11:03:40.893Z
Learning: In the Arkade wallet, the default password ("noah") is intentionally used as part of a progressive security model. New users can onboard without password friction using the default password, and once they accumulate 10,000+ sats, the app nudges them to set a custom password. This is a deliberate UX design decision to reduce onboarding barriers while encouraging security practices when users have meaningful value at stake.

Applied to files:

  • src/providers/wallet.tsx
  • src/lib/constants.ts
📚 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/Password.tsx
🧬 Code graph analysis (7)
src/components/Menu.tsx (2)
src/providers/nudge.tsx (1)
  • NudgeContext (12-20)
src/icons/RedDot.tsx (1)
  • RedDotIcon (1-10)
src/screens/Wallet/Index.tsx (2)
src/lib/constants.ts (1)
  • psaMessage (10-10)
src/components/AlertBox.tsx (1)
  • InfoBox (55-68)
src/providers/wallet.tsx (2)
src/providers/nudge.tsx (1)
  • NudgeContext (12-20)
src/lib/constants.ts (1)
  • minSatsToNudge (8-8)
src/App.tsx (5)
src/providers/nudge.tsx (1)
  • NudgeContext (12-20)
src/icons/Wallet.tsx (1)
  • WalletIcon (1-13)
src/components/FlexCol.tsx (1)
  • FlexCol (15-40)
src/icons/Apps.tsx (1)
  • AppsIcon (1-13)
src/icons/Settings.tsx (1)
  • SettingsIcon (1-13)
src/screens/Settings/Password.tsx (2)
src/providers/nudge.tsx (1)
  • NudgeContext (12-20)
src/components/Nudge.tsx (1)
  • NudgeComponent (7-19)
src/components/Nudge.tsx (4)
src/providers/nudge.tsx (2)
  • Nudge (5-10)
  • NudgeContext (12-20)
src/components/AlertBox.tsx (2)
  • AlertBox (14-45)
  • AlertText (47-53)
src/icons/RedDot.tsx (1)
  • RedDotIconAnimated (12-21)
src/components/FlexCol.tsx (1)
  • FlexCol (15-40)
src/index.tsx (2)
src/providers/nudge.tsx (1)
  • NudgeProvider (22-62)
src/App.tsx (1)
  • App (36-160)
⏰ 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). (1)
  • GitHub Check: Cloudflare Pages: wallet-bitcoin
🔇 Additional comments (11)
src/components/AlertBox.tsx (2)

14-53: AlertBox/AlertText public exports look consistent and safe

The new exports, click/dismiss wiring, and styling logic are all straightforward and type‑safe; DOMPurify usage and InfoBox integration remain intact. No issues from my side here.


55-60: InfoBox and CloseIcon tweaks are low‑risk UI refinements

Reducing InfoBox font size and adding a pointer cursor on the close SVG are purely visual changes and match the intent of a dismissible alert; no behavioural or safety concerns.

Also applies to: 70-82

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

55-55: PSA InfoBox rendering is straightforward and safe

Conditional rendering based solely on psaMessage and delegating HTML sanitization to InfoBox looks correct; no edge‑case or safety issues here.

src/App.tsx (1)

32-42: NudgeContext consumption and icon wiring are correct

Using nudges[Tabs.X]?.length > 0 with optional chaining is safe against missing keys, and passing the resulting boolean into the tab icons cleanly reflects nudge state without risking runtime errors. The integration with NudgeProvider in index.tsx looks coherent.

Also applies to: 136-151

src/components/Menu.tsx (1)

4-9: Menu nudge indicator integration is sound

Consuming nudges from NudgeContext and rendering <RedDotIcon /> when nudges[option]?.length > 0 is type‑safe and aligns with the nudge model (options keyed by SettingsOptions). No correctness issues here.

Also applies to: 16-19, 22-43

src/icons/RedDot.tsx (1)

1-21: RedDot icon components are simple and robust

Both static and animated red dot SVGs are self‑contained and side‑effect free; dependency on CSS variables is safe and failure‑tolerant. Looks good.

src/lib/constants.ts (2)

8-8: LGTM! Threshold aligns with progressive security model.

The reduction to 10,000 sats correctly implements the nudge threshold described in the project learnings, where users are encouraged to set a password once they have meaningful value at stake.

Based on learnings, the 10,000 sats threshold is part of the intentional progressive security UX design.


10-10: LGTM! Default PSA message added.

The default message provides helpful guidance to users about support resources.

src/components/Nudge.tsx (1)

7-19: LGTM! Clean nudge component implementation.

The component correctly integrates with NudgeContext for dismissal, uses appropriate UI components (AlertBox, AlertText, FlexCol), and displays the animated red dot icon.

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

86-96: LGTM! Proper nudge integration in password settings.

The nudge is correctly retrieved from context using the SettingsOptions.Password key and conditionally rendered before the error message. The optional chaining safely handles missing nudges.

src/icons/Settings.tsx (1)

1-10: LGTM! Red dot indicator added to settings icon.

The optional nudge prop correctly controls the visibility of the red circle badge, providing a visual indicator when nudges are present.

@bordalix bordalix marked this pull request as ready for review December 1, 2025 11:47
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/providers/wallet.tsx (1)

90-99: Add error handling to prevent unhandled promise rejections.

The promise chain for calcBatchLifetimeMs and calcNextRollover lacks error handling. If either promise rejects, it results in an unhandled rejection. The balance check effect on line 109 uses .catch(() => {}) for this purpose.

Apply this diff to add error handling:

   useEffect(() => {
     if (!initialized || !vtxos || !svcWallet) return
     const allVtxos = [...vtxos.spendable, ...vtxos.spent]
     calcBatchLifetimeMs(aspInfo, allVtxos).then((batchLifetimeMs) => {
       const thresholdMs = Math.floor((batchLifetimeMs * maxPercentage) / 100)
       calcNextRollover(vtxos.spendable, svcWallet, aspInfo).then((nextRollover) => {
         updateWallet((prev) => ({ ...prev, nextRollover, thresholdMs }))
       })
-    })
+    }).catch((err) => consoleError(err, 'Error computing rollover thresholds'))
   }, [initialized, vtxos, svcWallet, aspInfo])

Alternatively, consider using async/await with try/catch for better readability:

   useEffect(() => {
     if (!initialized || !vtxos || !svcWallet) return
-    const allVtxos = [...vtxos.spendable, ...vtxos.spent]
-    calcBatchLifetimeMs(aspInfo, allVtxos).then((batchLifetimeMs) => {
-      const thresholdMs = Math.floor((batchLifetimeMs * maxPercentage) / 100)
-      calcNextRollover(vtxos.spendable, svcWallet, aspInfo).then((nextRollover) => {
-        updateWallet((prev) => ({ ...prev, nextRollover, thresholdMs }))
+    const computeThresholds = async () => {
+      try {
+        const allVtxos = [...vtxos.spendable, ...vtxos.spent]
+        const batchLifetimeMs = await calcBatchLifetimeMs(aspInfo, allVtxos)
+        const thresholdMs = Math.floor((batchLifetimeMs * maxPercentage) / 100)
+        const nextRollover = await calcNextRollover(vtxos.spendable, svcWallet, aspInfo)
+        updateWallet((prev) => ({ ...prev, nextRollover, thresholdMs }))
+      } catch (err) {
+        consoleError(err, 'Error computing rollover thresholds')
+      }
+    }
+    computeThresholds()
-      })
-    })
   }, [initialized, vtxos, svcWallet, aspInfo])
🧹 Nitpick comments (1)
src/providers/wallet.tsx (1)

101-110: Consider adding addPasswordNudge to the dependency array.

The addPasswordNudge function is called inside the effect but is not listed in the dependency array. This violates the React hooks exhaustive-deps rule and could cause stale closure issues if the function reference changes.

If addPasswordNudge is stable (wrapped in useCallback in the provider), you can suppress the lint warning with a comment. Otherwise, add it to the dependencies:

   useEffect(() => {
     if (!initialized || !vtxos || !svcWallet) return
     svcWallet
       .getBalance()
       .then((balance) => {
         if (balance.total >= minSatsToNudge) addPasswordNudge()
       })
       .catch(() => {})
-  }, [initialized, vtxos, svcWallet, aspInfo])
+  }, [initialized, vtxos, svcWallet, aspInfo, addPasswordNudge])
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6626b18 and 141a0f7.

📒 Files selected for processing (1)
  • src/providers/wallet.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 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/providers/wallet.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/providers/wallet.tsx
🧬 Code graph analysis (1)
src/providers/wallet.tsx (3)
src/providers/nudge.tsx (1)
  • NudgeContext (12-20)
src/lib/wallet.ts (2)
  • calcBatchLifetimeMs (37-48)
  • calcNextRollover (21-35)
src/lib/constants.ts (2)
  • maxPercentage (9-9)
  • minSatsToNudge (8-8)
⏰ 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/providers/wallet.tsx (1)

18-19: LGTM!

The new imports for maxPercentage, minSatsToNudge, and NudgeContext are correctly added to support the nudge functionality.

@tiero
Copy link
Member

tiero commented Dec 1, 2025

an we enable it for things like Push Notifications and Nostr backup?

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 (2)
src/test/e2e/nudge.test.ts (2)

18-23: Selectors relying on .nth() and generic tags are brittle

The following selectors are tightly coupled to DOM structure and ordering:

  • Lines 18–23: page.locator('div').filter({ hasText: /^Copy address$/ }).nth(5).click()
  • Line 23: page.getByRole('img').nth(4).click() for triggering copy
  • Line 37: page.getByTestId('transactions-list').locator('div').first().click()

These will break easily if:

  • Additional "Copy address" labels are added or order changes
  • Icons/images are reordered
  • Extra wrapper <div>s are introduced inside the transactions list

Prefer more semantic and targeted locators, for example:

  • getByRole('button', { name: 'Copy address' }) or a dedicated data-testid for the copy action
  • A more specific role/name for the control that copies the address instead of img.nth(4)
  • Within transactions-list, target the clickable transaction row more explicitly (e.g., a data-testid on the row, or a role like row / listitem), rather than the first generic div

This should make the test more resilient to future UI changes.

Happy to propose concrete locator patterns once the underlying markup/roles or test IDs are known.

Also applies to: 23-23, 37-37


25-27: Small robustness and clarity improvements in faucet + clipboard assertions

A few optional cleanups that could make failures clearer and the test a bit leaner:

  • Lines 25–27: expect(boardingAddress).toBeDefined() followed by toBeTruthy() are redundant; a single toBeTruthy() (or stronger, a regex check for address format) is enough.
  • Lines 29–33: exec('nigiri faucet ...') is fire-and-forget. If the CLI is missing or fails, the test will eventually time out on UI waits but without surfacing the underlying error. Wrapping exec in a small promisify helper and await‑ing it (or at least logging errors in its callback) would make failures easier to diagnose.
  • Line 31: await page.waitForSelector('text=Payment received!') is followed by expect(...).toBeVisible(), which already has built-in waiting; you can rely on the expect alone.

These aren’t blockers but would improve the test’s signal and maintainability.

If you decide to tighten the faucet handling, please confirm the exact nigiri CLI behavior and exit codes for your environment, as those can differ across versions.

Also applies to: 29-33

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 141a0f7 and b601c14.

📒 Files selected for processing (2)
  • src/components/TransactionsList.tsx (1 hunks)
  • src/test/e2e/nudge.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/e2e/nudge.test.ts (1)
src/test/e2e/utils.ts (1)
  • readClipboard (3-16)
⏰ 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/components/TransactionsList.tsx (1)

114-121: Stable test hook for transactions list looks good

Adding data-testid='transactions-list' on the container is a clean, non-invasive way to drive E2E tests and matches its usage in nudge.test.ts. No behavioral impact on the component.

@bordalix
Copy link
Collaborator Author

bordalix commented Dec 1, 2025

an we enable it for things like Push Notifications and Nostr backup?

@tiero yes, the Nudge provider is the outer most provider, which means every other provider will be able to register nudges into it, for whatever reason they want (push notifications, Nostr backup, soon to expiry, etc).

The Nudge to be registered has the following interface:

export type Nudge = {
  options: SettingsOptions[]
  pages: Pages[]
  tabs: Tabs[]
  text: string
}

Which marks which Tabs and SettingsOptions (in setting's menu) should have the red dot.
When arriving to the Page the app will show a component with text and a discard button that will delete this specific nudge from the nudge repo.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b601c14 and edd4eba.

📒 Files selected for processing (1)
  • src/test/e2e/nudge.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 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/nudge.test.ts
🧬 Code graph analysis (1)
src/test/e2e/nudge.test.ts (1)
src/test/e2e/utils.ts (1)
  • readClipboard (3-16)
⏰ 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). (1)
  • GitHub Check: test

Comment on lines +29 to +33
// faucet
exec(`nigiri faucet ${boardingAddress} 0.0021`)
await page.waitForSelector('text=Payment received!')
await expect(page.getByText('Payment received!')).toBeVisible()
await expect(page.getByText('SATS received successfully')).toBeVisible()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for the faucet command.

The exec call on line 30 runs asynchronously without error handling or completion verification. If the nigiri faucet command fails (e.g., nigiri not installed, network issue, or invalid address), the test will hang indefinitely waiting for the "Payment received!" selector.

Wrap exec in a promise with error handling:

-  // faucet
-  exec(`nigiri faucet ${boardingAddress} 0.0021`)
-  await page.waitForSelector('text=Payment received!')
+  // faucet
+  await new Promise<void>((resolve, reject) => {
+    exec(`nigiri faucet ${boardingAddress} 0.0021`, (error, stdout, stderr) => {
+      if (error) {
+        reject(new Error(`Faucet command failed: ${error.message}`))
+        return
+      }
+      if (stderr) {
+        console.warn('Faucet stderr:', stderr)
+      }
+      resolve()
+    })
+  })
+  await page.waitForSelector('text=Payment received!')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// faucet
exec(`nigiri faucet ${boardingAddress} 0.0021`)
await page.waitForSelector('text=Payment received!')
await expect(page.getByText('Payment received!')).toBeVisible()
await expect(page.getByText('SATS received successfully')).toBeVisible()
// faucet
await new Promise<void>((resolve, reject) => {
exec(`nigiri faucet ${boardingAddress} 0.0021`, (error, stdout, stderr) => {
if (error) {
reject(new Error(`Faucet command failed: ${error.message}`))
return
}
if (stderr) {
console.warn('Faucet stderr:', stderr)
}
resolve()
})
})
await page.waitForSelector('text=Payment received!')
await expect(page.getByText('Payment received!')).toBeVisible()
await expect(page.getByText('SATS received successfully')).toBeVisible()
🤖 Prompt for AI Agents
In src/test/e2e/nudge.test.ts around lines 29 to 33, the exec call for `nigiri
faucet` is fired without awaiting completion or handling errors which can cause
the test to hang; change it to a promise-based exec (or promisify
child_process.exec), await the command, capture stdout/stderr and check the exit
code, and if the command errors or exits non-zero reject/throw to fail the test;
also add a reasonable timeout and optional retry/logging so failures surface
immediately instead of waiting for the selector.

@tiero
Copy link
Member

tiero commented Dec 1, 2025

Ok tested the branch deploy and don't see the red dots yet

@bordalix
Copy link
Collaborator Author

bordalix commented Dec 1, 2025

Ok tested the branch deploy and don't see the red dots yet

@tiero for now the only action adding a nudge is having a total balance > 100_000 sats and not having a defined password. Are you testing it with those conditions?

@bordalix
Copy link
Collaborator Author

bordalix commented Dec 1, 2025

@tiero

Screen.Recording.2025-12-01.at.14.47.09.mov

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/test/screens/wallet/send.test.tsx (1)

83-83: Prefer role-based query for the “Continue” button (optional)

The updated expectation is fine and matches the new UI copy. For slightly more robust and accessibility-aligned testing, consider asserting by role instead of raw text:

expect(screen.getByRole('button', { name: /continue/i })).toBeInTheDocument()

This will keep the intent (a button labeled “Continue”) even if surrounding layout or nearby text changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between edd4eba and 0d387b3.

📒 Files selected for processing (2)
  • src/providers/wallet.tsx (3 hunks)
  • src/test/screens/wallet/send.test.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/providers/wallet.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/screens/wallet/send.test.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

@tiero
Copy link
Member

tiero commented Dec 1, 2025

Lets add Notifications already ( will be useful when we have #204) and Nostr backup feature

@bordalix
Copy link
Collaborator Author

bordalix commented Dec 1, 2025

@tiero let's see what we need to show on those new notifications and react then?

@tiero
Copy link
Member

tiero commented Dec 1, 2025

Driving
User attention to enable

  • Web Push Notifications
  • Nostr Backups
  • Password
  • Support chat

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 (4)
src/providers/nudge.tsx (3)

29-38: Consider treating nudges as derived state from repo instead of maintaining both manually.

Right now updateNudges is called manually from addNudge/removeNudge. That couples each mutator to the derivation logic and makes future mutations easy to get wrong.

A more robust pattern is:

  • Keep only repo in state.
  • Use useMemo or useEffect to derive nudges from repo.

Conceptually:

-import { ReactNode, createContext, useState } from 'react'
+import { ReactNode, createContext, useMemo, useState } from 'react'
@@
-  const [repo, setRepo] = useState<Nudge[]>([])
-  const [nudges, setNudges] = useState<Record<string, Nudge[]>>({})
+  const [repo, setRepo] = useState<Nudge[]>([])
@@
-  const updateNudges = (repo: Nudge[]) => {
-    const map: Record<string, Nudge[]> = {}
-    repo.forEach((nudge) => {
-      nudge.options.forEach((option) => (map[option] = [...(map[option] || []), nudge]))
-      nudge.pages.forEach((page) => (map[page] = [...(map[page] || []), nudge]))
-      nudge.tabs.forEach((tab) => (map[tab] = [...(map[tab] || []), nudge]))
-    })
-    setNudges(map)
-  }
+  const nudges = useMemo(() => {
+    const map: Record<string, Nudge[]> = {}
+    repo.forEach((nudge) => {
+      nudge.options.forEach((option) => (map[option] = [...(map[option] || []), nudge]))
+      nudge.pages.forEach((page) => (map[page] = [...(map[page] || []), nudge]))
+      nudge.tabs.forEach((tab) => (map[tab] = [...(map[tab] || []), nudge]))
+    })
+    return map
+  }, [repo])

This removes the risk of forgetting to call updateNudges whenever repo changes and simplifies the mutator functions.


29-30: Re-evaluate JSON-stringify–based identity for nudges.

Using JSON.stringify in flat for equality checks works but has trade-offs:

  • It’s relatively expensive if the number of nudges grows.
  • It couples identity to property order and full structural equality, which can be brittle if callers construct equivalent Nudge objects with different key insertion orders.
  • For removeNudge, you already pass around the same object instance from context in most flows, so reference equality would likely suffice.

If you don’t need structural equality, consider:

  • Using reference equality where possible (n === nudge).
  • Or assigning a stable id field at creation time and comparing on that.

Example minimal change if identity-by-reference is enough:

-  const flat = (nudge: Nudge) => JSON.stringify(nudge)
@@
-    if (repo.find((n) => flat(n) === flat(nudge))) return // already exists
+    if (repo.includes(nudge)) return // already exists
@@
-    if (!repo.find((n) => flat(n) === flat(nudge))) return // not found
-    const filtered = repo.filter((n) => flat(n) !== flat(nudge))
+    if (!repo.includes(nudge)) return // not found
+    const filtered = repo.filter((n) => n !== nudge)

If you expect structurally identical but distinct instances, adding an explicit id is safer than relying on JSON.stringify.

Also applies to: 41-62


6-10: Context shape and provider value are clear; consider documenting texts semantics.

The Nudge type and NudgeContext API are straightforward and flexible. One small suggestion: since texts is an array (vs. a single text), it may help future contributors if you document whether these are intended as multiple bullet points, paragraphs, or ordered messages so UI components render them consistently.

Also applies to: 13-22, 64-68

src/components/Menu.tsx (1)

4-9: Menu nudge integration looks correct and idiomatic.

Using NudgeContext to gate RedDotIcon on nudges[option]?.length > 0 is type-safe given the Record<string, Nudge[]> map, and the nested FlexRow keeps the existing layout while adding the red dot before the arrow. No functional issues spotted.

If you want a tiny readability tweak, you could replace the ternary with nudges[option]?.length && <RedDotIcon />, but that’s purely stylistic.

Also applies to: 17-18, 37-48

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d387b3 and df68396.

📒 Files selected for processing (4)
  • src/components/Menu.tsx (3 hunks)
  • src/components/Nudge.tsx (1 hunks)
  • src/icons/RedDot.tsx (1 hunks)
  • src/providers/nudge.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/Nudge.tsx
  • src/icons/RedDot.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/providers/nudge.tsx (1)
src/lib/constants.ts (1)
  • minSatsToNudge (8-8)
src/components/Menu.tsx (4)
src/providers/nudge.tsx (1)
  • NudgeContext (13-23)
src/components/FlexRow.tsx (1)
  • FlexRow (18-50)
src/icons/RedDot.tsx (1)
  • RedDotIcon (1-10)
src/icons/Arrow.tsx (1)
  • ArrowIcon (1-7)
⏰ 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/providers/nudge.tsx (1)

47-55: Password nudge definition looks coherent and localized.

The addPasswordNudge helper cleanly encapsulates the advanced/password options, settings page/tab, and the multi-line texts content, which should make it easy to reuse and evolve as you add more nudge types (e.g., push, Nostr backup). No issues from a correctness standpoint.

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 (2)
src/App.tsx (1)

138-138: Optional: factor out per‑tab nudge checks for readability

The three nudges[Tabs.*]?.length > 0 checks are correct but a bit repetitive. You could optionally precompute them to make the JSX slightly cleaner.

For example, near the top of the component (after getting nudges):

const walletHasNudge = nudges[Tabs.Wallet]?.length > 0
const appsHasNudge = nudges[Tabs.Apps]?.length > 0
const settingsHasNudge = nudges[Tabs.Settings]?.length > 0

Then update the icon props:

-                  <WalletIcon nudge={nudges[Tabs.Wallet]?.length > 0} />
+                  <WalletIcon nudge={walletHasNudge} />
...
-                  <AppsIcon nudge={nudges[Tabs.Apps]?.length > 0} />
+                  <AppsIcon nudge={appsHasNudge} />
...
-                  <SettingsIcon nudge={nudges[Tabs.Settings]?.length > 0} />
+                  <SettingsIcon nudge={settingsHasNudge} />

Purely cosmetic; no behavior change.

Also applies to: 144-144, 150-150

src/components/AlertBox.tsx (1)

70-83: CloseIcon pointer styling improves click affordance

Adding an explicit cursor: 'pointer' on the SVG is a nice UX improvement and is correctly guarded by onClick being required in the props. If you want a bit more type safety, you could annotate style as React.CSSProperties, but that’s optional.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df68396 and 6dbe8c2.

📒 Files selected for processing (2)
  • src/App.tsx (3 hunks)
  • src/components/AlertBox.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/App.tsx (5)
src/providers/nudge.tsx (1)
  • NudgeContext (13-23)
src/icons/Wallet.tsx (1)
  • WalletIcon (1-13)
src/components/FlexCol.tsx (1)
  • FlexCol (16-46)
src/icons/Apps.tsx (1)
  • AppsIcon (1-13)
src/icons/Settings.tsx (1)
  • SettingsIcon (1-13)
⏰ 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). (1)
  • GitHub Check: test
🔇 Additional comments (4)
src/App.tsx (1)

32-41: NudgeContext wiring in App looks solid

Importing and consuming NudgeContext at the App root to read nudges is correct and keeps the tab icons in sync with nudge state. The default nudges: {} value also makes this safe if the provider is ever missing in tests or storybook.

src/components/AlertBox.tsx (3)

3-5: Imports for FlexRow and MegaphoneIcon look appropriate

Using FlexRow for layout and a dedicated MegaphoneIcon for alerts/nudges fits the component’s responsibilities and keeps icon semantics clear. No issues here.


14-53: Exporting AlertBox and AlertText as shared primitives makes sense

Promoting these to named exports is a good move for reusability (e.g., by the new nudge components). The props remain simple and flexible (icon as React.ReactNode, optional onClick/onDismiss), and the layout via FlexRow is straightforward. No functional concerns from this API exposure.


55-67: InfoBox styling/icon tweak is safe

Switching to MegaphoneIcon and slightly reducing the font size to 12px preserves structure and still wraps content in AlertBox with sanitized HTML. Behavior and security posture with DOMPurify.sanitize remain intact.

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 (2)
src/icons/RedDot.tsx (2)

1-18: Consider removing the negative margin positioning hack.

The negative marginLeft is a fragile positioning approach that may cause layout issues in different contexts. Additionally, the circle is slightly off-center in the viewBox (positioned at 10,10 in a 21×21 viewBox rather than 10.5,10.5).

Apply this diff to center the circle and remove the negative margin:

 export default function RedDotIcon() {
-  const style = {
-    marginLeft: '-0.5rem',
-  }
   return (
     <svg
       fill='none'
       width='1rem'
       height='1rem'
-      style={style}
       viewBox='0 0 21 21'
       data-testid='red-dot-icon'
       xmlns='http://www.w3.org/2000/svg'
     >
-      <circle cx='10' cy='10' r='3' fill='var(--red)' />
+      <circle cx='10.5' cy='10.5' r='3' fill='var(--red)' />
     </svg>
   )
 }

If the negative margin is intentional for specific alignment needs, consider documenting the reason or using a more explicit positioning approach via the parent component's CSS.


20-29: Add test ID for consistency and consider CSS variable fallbacks.

The animated variant lacks a data-testid attribute, creating inconsistency with RedDotIcon. Additionally, both components rely on CSS variables without fallback values, which could result in invisible dots if the variables are undefined.

Apply this diff to add the test ID and fallback values:

 export function RedDotIconAnimated() {
   const style = {
     animation: 'var(--animation-pulse)',
   }
   return (
-    <svg width='14' height='14' viewBox='0 0 35 35' fill='none' style={style} xmlns='http://www.w3.org/2000/svg'>
-      <circle cx='17.5' cy='17.5' r='17.5' fill='var(--red)' />
+    <svg width='14' height='14' viewBox='0 0 35 35' fill='none' style={style} data-testid='red-dot-icon-animated' xmlns='http://www.w3.org/2000/svg'>
+      <circle cx='17.5' cy='17.5' r='17.5' fill='var(--red, #ff0000)' />
     </svg>
   )
 }

Also consider adding the same fallback to RedDotIcon (line 15):

-      <circle cx='10.5' cy='10.5' r='3' fill='var(--red)' />
+      <circle cx='10.5' cy='10.5' r='3' fill='var(--red, #ff0000)' />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6dbe8c2 and 31fec4c.

📒 Files selected for processing (4)
  • src/components/ExpandAddresses.tsx (2 hunks)
  • src/icons/RedDot.tsx (1 hunks)
  • src/icons/Settings.tsx (2 hunks)
  • src/test/e2e/nudge.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/icons/Settings.tsx
  • src/test/e2e/nudge.test.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-28T15:58:17.245Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 142
File: src/lib/bip21.ts:7-13
Timestamp: 2025-08-28T15:58:17.245Z
Learning: In src/lib/bip21.ts, the encodeBip21 function is used by the wallet which always has an arkAddress available to pass to the function, while decodeBip21 is used to parse received possible BIP21 URIs which may not contain arkAddress. This explains why arkAddress is required in encodeBip21 but optional in the Bip21Decoded interface.

Applied to files:

  • src/components/ExpandAddresses.tsx
🧬 Code graph analysis (1)
src/components/ExpandAddresses.tsx (1)
src/components/FlexRow.tsx (1)
  • FlexRow (18-50)
⏰ 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/components/ExpandAddresses.tsx (1)

46-72: LGTM! Clean test identifier implementation.

The addition of test identifiers to the address lines is well-implemented:

  • The testId prop is correctly typed as optional
  • The pattern address-line-${testId} provides consistent, descriptive test selectors
  • Each address type (bip21, btc, ark, lightning) receives a meaningful identifier

This will facilitate targeted testing of individual address lines.

@bordalix
Copy link
Collaborator Author

bordalix commented Dec 1, 2025

@coderabbitai you can now review the test

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 1, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the e2e nudge test file
cat src/test/e2e/nudge.test.ts

Length of output: 1766


@bordalix, I've reviewed the e2e nudge test. Overall, it provides good coverage of the nudge feature flow. Here are my observations and suggestions:

✅ Strengths

  1. Clear test flow: The test logically follows the user journey from wallet creation to nudge validation
  2. Good assertions: Validates both the nudge badge on the tab and the red dot icons in the menu
  3. End-to-end validation: Tests the complete feature including the nudge text display

🔍 Issues & Suggestions

1. Race condition with faucet command (Critical)

// Line 23: exec is fire-and-forget
exec(`nigiri faucet ${boardingAddress} 0.0021`)
await page.waitForSelector('text=Payment received!')

The exec() call doesn't wait for completion. Consider wrapping it in a Promise:

await new Promise<void>((resolve, reject) => {
  exec(`nigiri faucet ${boardingAddress} 0.0021`, (error) => {
    if (error) reject(error)
    else resolve()
  })
})

2. Hard-coded timeout (Best Practice)

// Line 22: Avoid hard-coded delays
await page.waitForTimeout(500)

Replace with a more robust wait strategy or remove if the subsequent clipboard read is sufficient.

3. Redundant settings tab click

// Line 26
await page.getByTestId('tab-settings').click()
// Line 29: duplicate click
await page.getByTestId('tab-settings').click()

The second click appears unnecessary. If it's intentional (e.g., to re-trigger navigation), please add a comment explaining why.

4. Missing error handling

Consider adding error handling for the faucet command and potentially a timeout:

try {
  await new Promise<void>((resolve, reject) => {
    exec(`nigiri faucet ${boardingAddress} 0.0021`, (error) => {
      if (error) reject(error)
      else resolve()
    })
  })
} catch (error) {
  console.error('Faucet command failed:', error)
  throw error
}

5. Address validation could be more specific

expect(boardingAddress).toBeDefined()
expect(boardingAddress).toBeTruthy()

Consider validating the address format (e.g., starts with expected prefix for the network).

6. Test amount verification

The test name says "balance > 100_000 sats" and you're fauceting 0.0021 BTC = 210,000 sats. This is correct, but consider adding a comment or constant to make the relationship clear:

const minSatsToNudge = 100_000
const faucetAmount = 0.0021 // 210,000 sats (> minSatsToNudge)

Minor Note

The test doesn't include cleanup (e.g., deleting the wallet), which might be fine for e2e tests that run in isolated environments, but consider if this is intentional.

Would you like me to suggest a refactored version of the test addressing these issues?

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

🧹 Nitpick comments (1)
src/providers/nudge.tsx (1)

31-39: Consider consolidating state updates to avoid secondary renders.

Calling setNudges inside updateNudges, which is itself called from within setRepo callbacks, triggers two state updates and two renders for each add/remove operation. For small nudge counts this is acceptable, but it could be optimized by deriving nudges from repo using useMemo instead of maintaining separate state.

Optional refactor using useMemo:

const [repo, setRepo] = useState<Nudge[]>([])

const nudges = useMemo(() => {
  const map: Record<string, Nudge[]> = {}
  repo.forEach((nudge) => {
    nudge.options.forEach((option) => (map[option] = [...(map[option] || []), nudge]))
    nudge.pages.forEach((page) => (map[page] = [...(map[page] || []), nudge]))
    nudge.tabs.forEach((tab) => (map[tab] = [...(map[tab] || []), nudge]))
  })
  return map
}, [repo])

Then remove setNudges and updateNudges calls from addNudge and removeNudge.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31fec4c and f34c1bb.

📒 Files selected for processing (1)
  • src/providers/nudge.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/providers/nudge.tsx (1)
src/lib/constants.ts (1)
  • minSatsToNudge (8-8)
🪛 GitHub Check: test
src/providers/nudge.tsx

[warning] 26-26:
'repo' is assigned a value but never used


[warning] 26-26:
'repo' is assigned a value 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 (6)
src/providers/nudge.tsx (6)

13-23: LGTM!

The context definition provides a clean API surface with appropriate default values for nudge management.


26-27: Static analysis false positive.

The warning that repo is unused is a false positive—it's correctly used within the functional state update callbacks in addNudge and removeNudge.


41-48: LGTM! Past review addressed.

The functional state update pattern correctly prevents the stale-state bug identified in the previous review. All state updates now use the latest prev value.


50-58: LGTM!

The addPasswordNudge helper provides a convenient API for creating password-related nudges with appropriate targeting (settings tab, settings page, password and advanced options).


60-67: LGTM! Past review addressed.

The functional state update pattern correctly prevents the stale-state bug identified in the previous review.


69-74: LGTM!

The provider correctly exposes the nudge management API to consumers throughout the application.

Comment on lines +6 to 11
export type Nudge = {
options: SettingsOptions[]
pages: Pages[]
tabs: Tabs[]
texts: string[]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for usage of nudge.texts or nudge.text in the codebase
rg -n 'nudge\.texts?(?:\[|\.| )' --type tsx --type ts -A 2 -B 2

Repository: arkade-os/wallet

Length of output: 87


🏁 Script executed:

#!/bin/bash
# Search for usage of nudge.texts or nudge.text in the codebase
rg -n 'nudge\.texts?' -g '*.tsx' -g '*.ts' -A 2 -B 2

Repository: arkade-os/wallet

Length of output: 415


The Nudge type field texts: string[] is correctly implemented but conflicts with PR objectives.

The codebase actually uses nudge.texts as an array (see src/components/Nudge.tsx:14 where it's mapped over). However, the PR objectives document states the field should be text: string. Clarify which design is intended—if multiple text lines are needed (current implementation), update the PR objectives; if only a single text is required, refactor the type to text: string and update the consuming component accordingly.

🤖 Prompt for AI Agents
In src/providers/nudge.tsx around lines 6–11, the Nudge type currently defines
texts: string[] which conflicts with the PR objectives that require a single
text string; change the type to text: string and then update all consuming code
(notably src/components/Nudge.tsx where nudge.texts is currently mapped) to use
nudge.text (remove the map/array handling), update any places that construct
Nudge objects to provide a single text property, and adjust related tests and
documentation accordingly so the model and usage are consistent.

const [repo, setRepo] = useState<Nudge[]>([])
const [nudges, setNudges] = useState<Record<string, Nudge[]>>({})

const flat = (nudge: Nudge) => JSON.stringify(nudge)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace JSON.stringify deduplication with a more robust approach.

Using JSON.stringify for object equality is fragile because property order in JavaScript objects is not guaranteed to be consistent. Two Nudge objects with identical content but different construction order will produce different strings, allowing duplicates.

Consider a more reliable approach:

-  const flat = (nudge: Nudge) => JSON.stringify(nudge)
+  const flat = (nudge: Nudge) => {
+    return [
+      nudge.options.slice().sort().join(','),
+      nudge.pages.slice().sort().join(','),
+      nudge.tabs.slice().sort().join(','),
+      nudge.texts.slice().sort().join(','),
+    ].join('|')
+  }

Alternatively, implement a proper deep-equality check or use a library like lodash.isequal.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const flat = (nudge: Nudge) => JSON.stringify(nudge)
const flat = (nudge: Nudge) => {
return [
nudge.options.slice().sort().join(','),
nudge.pages.slice().sort().join(','),
nudge.tabs.slice().sort().join(','),
nudge.texts.slice().sort().join(','),
].join('|')
}
🤖 Prompt for AI Agents
In src/providers/nudge.tsx around line 29, the flat function uses
JSON.stringify(nudge) for deduplication which is brittle due to property order
variance; replace it with a robust approach such as implementing a
stable/canonical serializer that recursively sorts object keys before
stringifying (or simply use a deep-equality check via lodash.isequal to detect
duplicates), and update dedup logic to use that canonical key or isEqual
comparisons; if you choose lodash.isequal, add the dependency/import and use it
where duplicates are filtered, otherwise implement a small helper that sorts
keys recursively then JSON.stringify to produce consistent keys.

@bordalix bordalix mentioned this pull request Jan 6, 2026
@bordalix
Copy link
Collaborator Author

bordalix commented Jan 6, 2026

replaced by #296

@bordalix bordalix closed this Jan 6, 2026
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