Skip to content

[TASK-20903] Prod release SP-153 (cherry-picks) — ENS + consent client → main - #2569

Merged
jjramirezn merged 12 commits into
mainfrom
release/sp-153-cherry-picks
Jul 29, 2026
Merged

[TASK-20903] Prod release SP-153 (cherry-picks) — ENS + consent client → main#2569
jjramirezn merged 12 commits into
mainfrom
release/sp-153-cherry-picks

Conversation

@jjramirezn

@jjramirezn jjramirezn commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Release page: prod release sp 153

dev is not releaseable right now, so this branch cherry-picks onto main only what ships now (supersedes #2567 as the FE release vehicle). Pairs with backend peanut-api-ts #1255.

  • feat: chain-aware ENS resolution + visible resolved address in withdraw #2501 feat: chain-aware ENS resolution + visible resolved address in withdraw — Konrad's, already merged to dev. Its 3 real commits picked (the internal dev-merge skipped). One conflict resolved in Initial.withdraw.view.tsx: kept main's chain-change error-clearing effect AND the ENS re-resolution effect as separate effects — the final file state is byte-identical to dev's resolved version.
  • feat: consent-ledger echo + re-consent click-through (tos-v1 phase 2) #2456 feat: consent-ledger echo + re-consent click-through (tos-v1 phase 2) — still open against dev; its 5 commits picked clean. No dependency changes (package.json diff is scripts-only, lockfile untouched); no next-intl usage, so it's safe on main without the i18n work.

Notes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a re-consent modal for updated legal documents, including “Accept & continue” and “Not now” snoozing.
    • Enhanced card and passkey flows to record accepted legal documents.
    • Added chain-aware ENS resolution and chain-aware recipient validation.
    • Improved crypto withdrawal compatibility messaging with formatted destination address.
  • Bug Fixes
    • Prevented outdated consent prompts/content from showing due to stale async or account switching.
  • Tests
    • Added comprehensive test coverage for re-consent behavior, legal document metadata, ENS resolution, and consent echoing.

0xkkonrad and others added 8 commits July 29, 2026 10:43
ENS names can hold a different address per chain (ENSIP-11). The
withdraw flow resolved every name to its mainnet record and sent on
the selected chain — funds loss when the name points elsewhere on
that chain (reported by an external ENS-savvy tester with
test.ses.eth: mainnet and Arbitrum records differ).

- resolveEns/validateAndResolveRecipient accept the destination
  chainId and forward it to /ens/:name?chainId= (api-ts #1236).
- Switching chains after typing a name re-resolves it for the new
  chain instead of silently keeping the old address.
- The resolved address is now shown under the input as soon as the
  name validates, and again in the compatibility warning modal — the
  user sees where funds go BEFORE any warning/confirm step.

(cherry picked from commit f2c0aac)
…flight

Between switching chains and the new resolution landing, recipient.address
still holds the previous chain's address and Review stayed clickable —
the exact wrong-chain send this PR fixes, in a narrow race window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 679c359)
crypto-withdraw-confirm.test.tsx (new on dev) mocks general.utils with only
the functions the page used at the time; the compatibility-modal change now
also renders printableAddress from that module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 4e56b63)
The backend's new consent_records ledger stores what the user was
actually shown, so every acceptance surface now echoes the displayed
document versions instead of letting the server assume: signup sends
terms+privacy via the x-accepted-legal header (the ZeroDev SDK owns the
register/verify body), card apply sends the exact regional doc set from
CardTermsScreen, and versions+hashes are generated from the src/content
legal frontmatter at build time — no hardcoded versions anywhere.

Adds the ToS §17 re-consent flow: a blocking click-through modal when a
published document version moves past the user's last provably-accepted
one. Fails open — a dead consent endpoint never locks the app.

(cherry picked from commit e1bdcf9)
The single-line entries exceeded printWidth 120, so every predev run
left a prettier-dirty tree; one field per line keeps the generated file
byte-stable under formatting.

(cherry picked from commit c0a3448)
A logout followed by login as a different account in the same SPA
session skipped the second user's consent-status check.

(cherry picked from commit a0bccaf)
Every dev/build entrypoint now regenerates the legal-version constants
(dev:clean, dev:fallback, analyze bypassed the predev hook and could
ship stale version/hash echoes). ReConsentModal state is fully isolated
per account: switching users clears the doc list, checkbox, and error,
a slow status response for the previous account is discarded, and the
checkbox resets after a successful acceptance.

(cherry picked from commit be5545b)
…ent failures to Sentry

Review follow-ups (Hugo on #2456):

- ReConsentModal tests: fail-open on status failure (must never lock the
  app), accept failure keeps the retry path, stale-response discard on
  account switch (regression already caught once in review), undisplayable
  slug filtering, once-per-user-per-session check.
- consent service tests: pin the exact signup/card doc sets per region and
  that every echoed entry carries its own generated version+hash — silent
  drift here mis-ledgers legal consent.
- Both catch blocks now Sentry.captureException instead of console.error:
  a systematic /accept failure traps every user behind an undismissable
  modal, and prod must be able to see that.

(cherry picked from commit f06fd71)
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jul 29, 2026 5:32pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds legal-document version generation and consent recording, introduces a re-consent modal, propagates chain IDs for ENS resolution during withdrawals, and expands crypto withdrawal compatibility messaging with formatted destination addresses.

Changes

Consent ledger and re-consent

Layer / File(s) Summary
Legal metadata generation
package.json, scripts/generate-legal-versions.mjs
Generates typed legal-document versions and SHA-256 hashes, then runs generation before development, analysis, and build phases.
Consent service and submission contracts
src/services/consent.ts, src/services/__tests__/consent.test.ts, src/services/rain.ts, src/app/(mobile-ui)/card/page.tsx, src/hooks/useZeroDev.ts
Defines consent APIs and document helpers, validates generated metadata, and sends accepted legal documents through card and passkey flows.
Re-consent modal lifecycle
src/components/Global/ReConsentModal/*, src/app/(mobile-ui)/layout.tsx, src/constants/analytics.consts.ts
Checks consent status, displays eligible outdated documents, submits acceptance, records modal events, and handles failures and account changes.

Chain-aware ENS resolution

Layer / File(s) Summary
Chain-specific ENS resolution contract
src/app/actions/ens.ts, src/lib/validation/recipient.ts, src/lib/validation/recipient.test.ts
Passes an optional chain ID through recipient validation and conditionally includes it in ENS resolution requests.
Withdrawal recipient synchronization
src/components/Global/GeneralRecipientInput/index.tsx, src/components/Withdraw/views/Initial.withdraw.view.tsx
Re-resolves ENS names after chain changes, prevents stale results from updating state, and displays the resolved address.

Crypto withdrawal compatibility messaging

Layer / File(s) Summary
Destination compatibility warning
src/app/(mobile-ui)/withdraw/crypto/page.tsx, src/app/(mobile-ui)/withdraw/crypto/__tests__/crypto-withdraw-confirm.test.tsx
Adds structured compatibility guidance and conditionally shows a shortened withdrawal destination address.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReConsentModal
  participant consentApi
  participant ConsentAPI
  ReConsentModal->>consentApi: getStatus()
  consentApi->>ConsentAPI: GET /users/consent/status
  ConsentAPI-->>consentApi: consent status
  consentApi-->>ReConsentModal: outdated documents
  ReConsentModal->>consentApi: accept(documents)
  consentApi->>ConsentAPI: POST /users/consent/accept
  ConsentAPI-->>consentApi: recorded count
  consentApi-->>ReConsentModal: acceptance result
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: hugo0

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is specific and aligned with the cherry-picked ENS and consent-related release changes landing on main.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/sp-153-cherry-picks

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: dependency version conflict. Check your lock file or package.json.


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

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6308.27 → 6334.45 (+26.18)
Findings: +7 net (+67 new, -60 resolved)

🆕 New findings (67)

  • critical complexity — src/app/(mobile-ui)/card/page.tsx — CC 126, MI 57.18, SLOC 478
  • critical complexity — src/app/(mobile-ui)/withdraw/crypto/page.tsx — CC 89, MI 52.01, SLOC 376
  • critical complexity — src/services/rain.ts — CC 67, MI 59.94, SLOC 264
  • high hotspot — src/app/(mobile-ui)/card/page.tsx — 44 commits, +1093/-496 lines since 6 months ago
  • high complexity — src/components/Withdraw/views/Initial.withdraw.view.tsx — CC 43, MI 58.63, SLOC 134
  • high method-complexity — src/app/(mobile-ui)/layout.tsx:37 — CC 38 SLOC 65
  • high hotspot — src/constants/analytics.consts.ts — 36 commits, +323/-7 lines since 6 months ago
  • high hotspot — src/app/(mobile-ui)/withdraw/crypto/page.tsx — 31 commits, +400/-219 lines since 6 months ago
  • high hotspot — src/hooks/useZeroDev.ts — 31 commits, +246/-175 lines since 6 months ago
  • high complexity — src/constants/analytics.consts.ts — CC 1, MI 32.83, SLOC 175
  • medium react-long-component — src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage is 614 lines — split it
  • medium high-mdd — src/app/(mobile-ui)/card/page.tsx:55 — CardPage: MDD 130.8 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage: MDD 105.1 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/layout.tsx:37 — Layout: MDD 70.8 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/useZeroDev.ts:47 — useZeroDev: MDD 58.3 (uses across many lines from declarations)
  • medium high-dlt — src/app/(mobile-ui)/card/page.tsx:55 — CardPage: DLT 57 (calls 57 distinct functions — high context load)
  • medium high-dlt — src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage: DLT 56 (calls 56 distinct functions — high context load)
  • medium high-dlt — src/hooks/useZeroDev.ts:47 — useZeroDev: DLT 49 (calls 49 distinct functions — high context load)
  • medium high-mdd — src/components/Global/GeneralRecipientInput/index.tsx:38 — GeneralRecipientInput: MDD 42.8 (uses across many lines from declarations)
  • medium high-mdd — src/components/Withdraw/views/Initial.withdraw.view.tsx:27 — InitialWithdrawView: MDD 33.5 (uses across many lines from declarations)

…and 47 more.

✅ Resolved (60)

  • src/app/(mobile-ui)/card/page.tsx — CC 125, MI 57.27, SLOC 475
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx — CC 88, MI 52.06, SLOC 375
  • src/services/rain.ts — CC 65, MI 60.08, SLOC 262
  • src/app/(mobile-ui)/card/page.tsx — 43 commits, +1085/-494 lines since 6 months ago
  • src/app/(mobile-ui)/layout.tsx:36 — CC 38 SLOC 65
  • src/constants/analytics.consts.ts — 35 commits, +322/-7 lines since 6 months ago
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx — 30 commits, +380/-217 lines since 6 months ago
  • src/components/Withdraw/views/Initial.withdraw.view.tsx — CC 30, MI 58.4, SLOC 98
  • src/hooks/useZeroDev.ts — 30 commits, +241/-174 lines since 6 months ago
  • src/constants/analytics.consts.ts — CC 1, MI 32.9, SLOC 174
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage is 596 lines — split it
  • src/app/(mobile-ui)/card/page.tsx:54 — CardPage: MDD 129.3 (uses across many lines from declarations)
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage: MDD 105.0 (uses across many lines from declarations)
  • src/app/(mobile-ui)/layout.tsx:36 — Layout: MDD 70.8 (uses across many lines from declarations)
  • src/hooks/useZeroDev.ts:46 — useZeroDev: MDD 57.7 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:54 — CardPage: DLT 56 (calls 56 distinct functions — high context load)
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage: DLT 55 (calls 55 distinct functions — high context load)
  • src/hooks/useZeroDev.ts:46 — useZeroDev: DLT 47 (calls 47 distinct functions — high context load)
  • src/components/Global/GeneralRecipientInput/index.tsx:35 — GeneralRecipientInput: MDD 40.1 (uses across many lines from declarations)
  • src/services/rain.ts:335 — rainRequest CC 29 SLOC 62

…and 40 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/components/Global/ReConsentModal/index.tsx 0.0 7.9 +7.9
src/constants/legal-versions.generated.ts 0.0 5.0 +5.0
src/components/Global/ReConsentModal/utils.ts 0.0 4.9 +4.9
src/services/consent.ts 0.0 4.7 +4.7
src/app/actions/ens.ts 5.3 6.7 +1.4
src/components/Withdraw/views/Initial.withdraw.view.tsx 10.7 11.3 +0.6

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2265 ran, 0 failed, 0 skipped, 38.2s

📊 Coverage (unit)

metric %
statements 62.1%
branches 45.5%
functions 51.7%
lines 62.5%
⏱ 10 slowest test cases
time test
3.9s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.3s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.4s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.2s src/utils/__tests__/demo-balance.test.ts › keeps a spent-down balance across a cold start within the TTL
0.2s src/utils/__tests__/demo-balance.test.ts › resetDemoBalance refills and restarts the TTL window
0.2s src/utils/__tests__/demo-balance.test.ts › debits and floors at zero
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@jjramirezn jjramirezn changed the title Prod release SP-153 (cherry-picks) — ENS + consent client → main [TASK-20903] Prod release SP-153 (cherry-picks) — ENS + consent client → main Jul 29, 2026
@notion-workspace

Copy link
Copy Markdown

PROD RELEASE

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Withdraw/views/Initial.withdraw.view.tsx`:
- Around line 80-108: Update the selected-chain resolution effect around
validateAndResolveRecipient so in-flight validation from a previous chain is
cancelled or ignored when the validator or chain changes, preventing stale
results from updating recipient or re-enabling Review. Ensure
ValidatedInput/checkAddress participates in this invalidation, and add a
regression test covering a chain switch while validation is pending.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4898fe75-3425-4232-9dbc-0fd23a49edcb

📥 Commits

Reviewing files that changed from the base of the PR and between c4b041a and b6e9845.

⛔ Files ignored due to path filters (1)
  • src/constants/legal-versions.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (18)
  • package.json
  • scripts/generate-legal-versions.mjs
  • src/app/(mobile-ui)/card/page.tsx
  • src/app/(mobile-ui)/layout.tsx
  • src/app/(mobile-ui)/withdraw/crypto/__tests__/crypto-withdraw-confirm.test.tsx
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx
  • src/app/actions/ens.ts
  • src/components/Global/GeneralRecipientInput/index.tsx
  • src/components/Global/ReConsentModal/__tests__/index.test.tsx
  • src/components/Global/ReConsentModal/index.tsx
  • src/components/Withdraw/views/Initial.withdraw.view.tsx
  • src/constants/analytics.consts.ts
  • src/hooks/useZeroDev.ts
  • src/lib/validation/recipient.test.ts
  • src/lib/validation/recipient.ts
  • src/services/__tests__/consent.test.ts
  • src/services/consent.ts
  • src/services/rain.ts

Comment on lines +80 to +108
useEffect(() => {
if (prevResolvedChainRef.current === selectedChainID) return
prevResolvedChainRef.current = selectedChainID
if (addressFamily !== 'evm' || !recipient.name) return

const name = recipient.name
// Gate Review while the previous chain's address is still in state —
// clicking it mid-resolution would send to that address.
setIsValidRecipient(false)
let stale = false
validateAndResolveRecipient(name, true, 'evm', selectedChainID)
.then((validation) => {
if (stale) return
setRecipient({ name, address: validation.resolvedAddress })
setIsValidRecipient(true)
})
.catch(() => {
if (stale) return
setRecipient({ name: undefined, address: '' })
setIsValidRecipient(false)
setError({
showError: true,
errorMessage: 'Could not resolve the ENS name for the selected network',
})
})
return () => {
stale = true
}
}, [selectedChainID, addressFamily, recipient.name, setRecipient, setIsValidRecipient, setError])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C4 'function ValidatedInput|const ValidatedInput|export.*ValidatedInput' src
rg -n -C6 'validate={checkAddress}|onUpdate={onInputUpdate}' src/components
rg -n -C5 'InitialWithdrawView|GeneralRecipientInput' --glob '*test*' src

Repository: peanutprotocol/peanut-ui

Length of output: 4851


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- ValidatedInput outline ---'
ast-grep outline src/components/Global/ValidatedInput/index.tsx --view expanded || true

echo '--- GeneralRecipientInput outline ---'
ast-grep outline src/components/Global/GeneralRecipientInput/index.tsx --view expanded || true

echo '--- Withdraw view outline ---'
ast-grep outline src/components/Withdraw/views/Initial.withdraw.view.tsx --view expanded || true

Repository: peanutprotocol/peanut-ui

Length of output: 958


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,260p' src/components/Global/ValidatedInput/index.tsx
printf '\n---\n'
sed -n '1,260p' src/components/Global/GeneralRecipientInput/index.tsx
printf '\n---\n'
sed -n '1,220p' src/components/Withdraw/views/Initial.withdraw.view.tsx
printf '\n---\n'
sed -n '1,260p' src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx

Repository: peanutprotocol/peanut-ui

Length of output: 36925


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  src/components/Global/ValidatedInput/index.tsx \
  src/components/Global/GeneralRecipientInput/index.tsx \
  src/components/Withdraw/views/Initial.withdraw.view.tsx \
  src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx
do
  echo "===== $f ====="
  nl -ba "$f" | sed -n '1,260p'
  echo
done

Repository: peanutprotocol/peanut-ui

Length of output: 262


Prevent stale chain validations from overwriting the recipient ValidatedInput keeps an in-flight checkAddress promise alive across chainId changes, so a previous chain’s ENS result can still write the old resolved address back into recipient and re-enable Review. Cancel/ignore pending validations when the validator changes, and add a chain-switch race regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Withdraw/views/Initial.withdraw.view.tsx` around lines 80 -
108, Update the selected-chain resolution effect around
validateAndResolveRecipient so in-flight validation from a previous chain is
cancelled or ignored when the validator or chain changes, preventing stale
results from updating recipient or re-enabling Review. Ensure
ValidatedInput/checkAddress participates in this invalidation, and add a
regression test covering a chain switch while validation is pending.

Hugo0 added 3 commits July 29, 2026 17:50
The modal shipped as a hard gate: preventClose, hidden close button, and a
single Accept CTA. It mounts in the mobile-ui layout, so it covered the whole
app, including /withdraw.

That conflicts with the terms it enforces. §17.2 gives material changes a
30-day runway and offers the click-through as a way to accept sooner, by
choice. We published on 2026-07-15, so the new terms take effect around
2026-08-14; a hard gate enforces them about two weeks early. §17.3 says a user
who does not agree must stop using the Services. For a non-custodial wallet
that must still leave a path to their own funds.

So the prompt now defers instead of blocking:

- "Not now" (plus close, backdrop and Escape) dismisses it. The checkbox does
  not gate the exit.
- A dismissal snoozes the prompt for 3 days per user, in localStorage. It
  never writes a ledger row, because a refusal is not consent.
- Accept reports modal_cta_clicked, not modal_dismissed. Both events existed
  already. We need the accept-vs-refuse ratio to measure the rollout.
- Copy no longer says "To keep using Peanut". That claim is no longer true.

Accept stays the primary action: purple, shadow, first in the stack.
…interval

"Not now" now snoozes until the documents actually take effect — the
frontmatter version plus the 30 days our own ToS §17.2 promises — instead of a
flat 3 days. A document posted today buys the user its full notice period.

Past that date §17.3 already makes continued use acceptance, so the prompt only
still asks in order to record explicit consent. That earns a gentle cadence,
not a prompt on every app open, so the snooze floors at MIN_SNOOZE_DAYS.
The committed file said card-terms-international was 2026-06-01. The content
submodule this branch pins says 2026-07-14 — the §8.4 Nigerian Users clause.
`predev`/`prebuild` regenerate this file, so any dev server or Vercel build
already produced the newer value and left the tree dirty.

WARNING, cross-repo: peanut-api CURRENT_LEGAL_VERSIONS still says 2026-06-01
for the same document. sanitizeEchoedDocuments treats a client version newer
than the server's as an attack and clamps it, dropping the hash. So the ledger
would record 2026-06-01 for users who were shown 2026-07-14, and the clause
update would never trigger re-consent. The constant needs the same bump in
peanut-api-ts#1255.
fix(re-consent): make the terms prompt escapable, per our own ToS §17

@Hugo0 Hugo0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved.

Verified on the branch tip:

  • Consent ledger echo, ENS chain-aware resolution, and the withdraw resolved-address surface all read clean.
  • #2579 is merged in, so the re-consent modal ships escapable — "Not now" + close/backdrop/Escape, deferring to the document's effective date, and a dismissal writes no ledger row. That was the blocker; it's resolved.
  • legal-versions.generated.ts now matches the pinned content submodule (card-terms-international = 2026-07-14).

One cross-repo note carried over from the paired PR, not blocking this one: peanut-api CURRENT_LEGAL_VERSIONS['card-terms-international'] is still 2026-06-01, so this FE will echo 2026-07-14 and the backend will clamp it. Details on peanut-api-ts#1255 — worth landing that one-liner before this reaches users.

Deploy order unchanged: BE + migration first, then this.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Global/ReConsentModal/index.tsx`:
- Around line 58-59: In src/components/Global/ReConsentModal/index.tsx lines
58-59, handle logout by clearing modal state, resetting lastCheckedUserId, and
invalidating any pending status request. In lines 90-107, capture the initiating
user/session token and guard success, failure, and finally updates so stale
async completions are ignored after authentication changes; add a regression
test covering an account switch during acceptance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 07e9e0c3-8333-4420-a831-1124a49a24f9

📥 Commits

Reviewing files that changed from the base of the PR and between b6e9845 and 46f321a.

⛔ Files ignored due to path filters (1)
  • src/constants/legal-versions.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (3)
  • src/components/Global/ReConsentModal/__tests__/index.test.tsx
  • src/components/Global/ReConsentModal/index.tsx
  • src/components/Global/ReConsentModal/utils.ts

Comment on lines +58 to +59
// a recent "Not now" defers the prompt — don't even spend the request
if (isReConsentSnoozed(userId)) return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invalidate re-consent work across auth-session changes. The modal tracks a user ID for status requests but does not invalidate state or async completions when authentication changes.

  • src/components/Global/ReConsentModal/index.tsx#L58-L59: on logout, clear modal state, reset lastCheckedUserId, and invalidate the pending status request.
  • src/components/Global/ReConsentModal/index.tsx#L90-L107: capture the initiating user/session token and ignore stale success, failure, and finally updates; add an account-switch-during-accept regression test.
📍 Affects 1 file
  • src/components/Global/ReConsentModal/index.tsx#L58-L59 (this comment)
  • src/components/Global/ReConsentModal/index.tsx#L90-L107
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Global/ReConsentModal/index.tsx` around lines 58 - 59, In
src/components/Global/ReConsentModal/index.tsx lines 58-59, handle logout by
clearing modal state, resetting lastCheckedUserId, and invalidating any pending
status request. In lines 90-107, capture the initiating user/session token and
guard success, failure, and finally updates so stale async completions are
ignored after authentication changes; add a regression test covering an account
switch during acceptance.

@jjramirezn
jjramirezn merged commit e8eca13 into main Jul 29, 2026
25 of 27 checks passed
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