Skip to content

Consent-ledger echo + re-consent click-through (tos-v1 phase 2)#2455

Closed
jjramirezn wants to merge 3 commits into
devfrom
feat/consent-ledger
Closed

Consent-ledger echo + re-consent click-through (tos-v1 phase 2)#2455
jjramirezn wants to merge 3 commits into
devfrom
feat/consent-ledger

Conversation

@jjramirezn

@jjramirezn jjramirezn commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Consent-ledger client: version echo + re-consent click-through (tos-v1 phase 2)

Summary

Frontend half of the consent ledger (peanut-api consent_records). The app now
tells the backend exactly which legal-document revisions it displayed at every
acceptance surface, and gains the ToS §17 re-consent click-through.

  • Generated versions: scripts/generate-legal-versions.mjs (predev +
    prebuild, output committed) derives slug → { version, hash } from the
    src/content legal frontmatter (last_updated) + sha256 of each en.md.
    No more hardcoded versions anywhere; bumping the content submodule refreshes
    the constants on the next build.
  • Signup echo: the ZeroDev SDK owns the /passkeys/register/verify body,
    so useZeroDev.handleRegister sends the displayed terms+privacy versions via
    the x-accepted-legal header (passkeyServerHeaders).
  • Card apply echo: rainApi.applyForCard accepts acceptedDocuments;
    the card page sends the exact set CardTermsScreen showed for the user's
    region (US: card-terms-us + card-esign + card-privacy; international:
    card-terms-international + card-esign).
  • Re-consent: new ReConsentModal (mounted in the mobile-ui layout) calls
    GET /users/consent/status once per session for authenticated users; when a
    published version moved past the user's last provably-accepted one, it shows
    a blocking ActionModal (preventClose, checkbox gate) listing the updated
    docs and records acceptance via POST /users/consent/accept.
  • New client-side service src/services/consent.ts (no server actions).

Backend coupling

Requires peanut-api PRs "consent ledger" + "re-consent endpoints". Safe to ship
in either order: the apply/signup echoes are optional fields/headers the old
backend ignores, and the consent-status fetch failing (404) just logs and never
blocks the app.

Risks

  • All pre-ledger users will see the re-consent modal once after the
    backend's status endpoint ships — by design (nobody has provable consent
    yet); acceptance backfills the ledger for the whole user base. If product
    wants a quieter rollout, gate the modal mount before merging.
  • The modal is deliberately unclosable until accepted (ToS §17 click-through);
    a consent-status fetch failure keeps the app usable (fail-open).
  • handleApply gained pendingTerms in its deps — callback identity now
    changes when the terms screen opens; downstream useCallbacks already
    depend on handleApply so behavior is unchanged.

QA

  1. pnpm dev → predev regenerates src/constants/legal-versions.generated.ts
    (7 docs).
  2. Fresh signup → backend consent_records gets terms + privacy rows with
    version 2026-07-15 and the sha256 hashes from the generated file.
  3. Card apply (US and international) → ledger rows for the region's doc set,
    surface='card-apply'.
  4. Manually backdate a user's ledger row (or use a pre-ledger user) → app load
    shows the re-consent modal; links open the docs (web: new tab, native:
    in-app browser); accept → surface='re-consent' rows; modal never
    reappears.
  5. Kill the backend → app loads normally, no modal, console warning only.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added re-consent prompts when updated legal documents require acceptance.
    • Users can review document links and accept updated terms before continuing.
    • Legal document versions and verification details are tracked during signup and card applications.
    • Card applications now record the specific documents accepted with the terms.
  • Bug Fixes
    • Ensured consent information is included during passkey registration and card applications.
  • Analytics
    • Added tracking for re-consent modal display and dismissal events.

jjramirezn and others added 3 commits July 17, 2026 17:24
Hotfix port to main of peanut-ui#2438 (branch was dev-based; every
touched file is byte-identical between dev and main, so this is the
same reviewed content squashed onto main). Pairs with peanut-api-ts
hotfix/rain-poa-selfheal — deploy the API first.

When the rain rail carries the sumsub:proof_of_address next-action, the
card status screens (requires-info / rejected) render a primary
"Upload proof of address" CTA driving a dedicated lightweight flow:
resubmit action -> second SumsubKycWrapper -> inline initiation errors
-> optimistic "we received your document" banner (auto-reset when the
backend state takes over). Double-click guard; analytics tagged
source: poa-self-heal.

Full review trail on #2438.
feat(card): proof-of-address upload on stuck Rain applications
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.
@vercel

vercel Bot commented Jul 20, 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 20, 2026 4:24pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This change generates legal document metadata, adds consent APIs and document payload helpers, propagates consent records through signup and card applications, and mounts a re-consent modal for authenticated users.

Changes

Legal consent tracking

Layer / File(s) Summary
Legal metadata and consent service
scripts/generate-legal-versions.mjs, package.json, src/services/consent.ts
Build and development hooks generate legal document versions and hashes; consent types, document helpers, and status/acceptance API methods are added.
Signup and card consent propagation
src/hooks/useZeroDev.ts, src/services/rain.ts, src/app/(mobile-ui)/card/page.tsx
Signup passkey registration and accepted card terms now send legal document records.
Authenticated re-consent modal
src/components/Global/ReConsentModal/index.tsx, src/app/(mobile-ui)/layout.tsx, src/constants/analytics.consts.ts
The mobile shell mounts a blocking re-consent modal that loads outdated documents, records acceptance, and emits modal analytics.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AuthenticatedUser
  participant ReConsentModal
  participant consentApi
  participant PostHog
  AuthenticatedUser->>ReConsentModal: open mobile layout
  ReConsentModal->>consentApi: getStatus
  consentApi-->>ReConsentModal: documents needing acceptance
  ReConsentModal->>PostHog: modal shown
  AuthenticatedUser->>ReConsentModal: check and accept
  ReConsentModal->>consentApi: accept documents
  ReConsentModal->>PostHog: modal dismissed
Loading

Possibly related PRs

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: consent-ledger integration plus a re-consent flow.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/consent-ledger

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

@github-actions

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6224 → 6241.01 (+17.01)
Findings: +4 net (+46 new, -42 resolved)

🆕 New findings (46)

  • critical complexity — src/app/(mobile-ui)/card/page.tsx — CC 126, MI 57.18, SLOC 478
  • critical complexity — src/services/rain.ts — CC 66, MI 60.12, SLOC 260
  • high hotspot — src/app/(mobile-ui)/card/page.tsx — 45 commits, +1278/-496 lines since 6 months ago
  • high method-complexity — src/app/(mobile-ui)/layout.tsx:37 — CC 38 SLOC 65
  • high hotspot — src/constants/analytics.consts.ts — 33 commits, +309/-7 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 33.31, SLOC 168
  • 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)/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/hooks/useZeroDev.ts:47 — useZeroDev: DLT 49 (calls 49 distinct functions — high context load)
  • medium method-complexity — src/services/rain.ts:328 — rainRequest CC 28 SLOC 61
  • medium high-mdd — src/hooks/useZeroDev.ts:59 — handleRegister: MDD 25.0 (uses across many lines from declarations)
  • medium hotspot — src/services/rain.ts — 24 commits, +840/-95 lines since 6 months ago
  • medium complexity — src/components/Global/ReConsentModal/index.tsx — CC 23, MI 62.04, SLOC 105
  • medium high-mdd — src/services/rain.ts:328 — rainRequest: MDD 23.2 (uses across many lines from declarations)
  • medium method-complexity — src/app/(mobile-ui)/card/page.tsx:548 — CC 18 SLOC 51
  • medium react-effect-derives-state — src/app/(mobile-ui)/card/page.tsx:269 — small useEffect that only sets state from deps
  • medium react-effect-derives-state — src/app/(mobile-ui)/layout.tsx:62 — useEffect with empty deps + setState — derived state anti-pattern

…and 26 more.

✅ Resolved (42)

  • src/app/(mobile-ui)/card/page.tsx — CC 125, MI 57.27, SLOC 475
  • src/services/rain.ts — CC 64, MI 60.26, SLOC 258
  • src/app/(mobile-ui)/card/page.tsx — 44 commits, +1270/-494 lines since 6 months ago
  • src/app/(mobile-ui)/layout.tsx:36 — CC 38 SLOC 65
  • src/constants/analytics.consts.ts — 32 commits, +308/-7 lines since 6 months ago
  • src/hooks/useZeroDev.ts — 30 commits, +241/-174 lines since 6 months ago
  • src/constants/analytics.consts.ts — CC 1, MI 33.38, SLOC 167
  • src/app/(mobile-ui)/card/page.tsx:54 — CardPage: MDD 129.3 (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/hooks/useZeroDev.ts:46 — useZeroDev: DLT 47 (calls 47 distinct functions — high context load)
  • src/services/rain.ts:327 — rainRequest CC 28 SLOC 61
  • src/hooks/useZeroDev.ts:58 — handleRegister: MDD 24.7 (uses across many lines from declarations)
  • src/components/Card/share-asset/ShareAssetD3.tsx — 24 commits, +1525/-1137 lines since 6 months ago
  • src/services/rain.ts:327 — rainRequest: MDD 23.2 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:542 — CC 18 SLOC 51
  • src/app/(mobile-ui)/card/page.tsx:268 — small useEffect that only sets state from deps
  • src/app/(mobile-ui)/layout.tsx:61 — useEffect with empty deps + setState — derived state anti-pattern
  • src/app/(mobile-ui)/layout.tsx:75 — direct DOM: document.querySelector

…and 22 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/components/Global/ReConsentModal/index.tsx 0.0 6.4 +6.4
src/constants/legal-versions.generated.ts 0.0 4.8 +4.8
src/services/consent.ts 0.0 4.7 +4.7

@github-actions

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2000 ran, 0 failed, 0 skipped, 33.2s

📊 Coverage (unit)

metric %
statements 59.5%
branches 43.3%
functions 48.3%
lines 59.8%
⏱ 10 slowest test cases
time test
3.7s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.1s 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.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/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.2s 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 › auto-refills a stored balance that has no timestamp (legacy install)
0.2s src/utils/__tests__/demo-balance.test.ts › starts at the full balance on a fresh install and stamps a timestamp
0.2s src/utils/__tests__/url.utils.test.ts › uses the public BASE_URL in Capacitor, not the localhost WebView origin
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@jjramirezn
jjramirezn changed the base branch from main to dev July 20, 2026 16:48
@jjramirezn

Copy link
Copy Markdown
Contributor Author

Superseded by the dev-based branch feat/tos-v1-consent-client.

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.

2 participants