Skip to content

feat: consent-ledger echo + re-consent click-through (tos-v1 phase 2)#2456

Open
jjramirezn wants to merge 4 commits into
devfrom
feat/tos-v1-consent-client
Open

feat: consent-ledger echo + re-consent click-through (tos-v1 phase 2)#2456
jjramirezn wants to merge 4 commits into
devfrom
feat/tos-v1-consent-client

Conversation

@jjramirezn

@jjramirezn jjramirezn commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Client half of the consent ledger (peanut-api #1209, tos-v1 phase 2): every acceptance surface now echoes the exact legal-document revisions it displayed, and the ToS §17 re-consent click-through ships.

  • 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 hardcoded versions; bumping the content submodule refreshes the constants on next build.
  • Signup echo: the ZeroDev SDK owns the /passkeys/register/verify body, so useZeroDev.handleRegister sends displayed terms+privacy versions via the x-accepted-legal header (passkeyServerHeaders).
  • Card apply echo: rainApi.applyForCard gains acceptedDocuments; the card page sends the exact set CardTermsScreen showed (US: card-terms-us + card-esign + card-privacy; international: card-terms-international + card-esign).
  • Re-consent: new ReConsentModal (mobile-ui layout) checks GET /users/consent/status once per session; when a published version passed the user's last provably-accepted one, a blocking checkbox click-through lists the updated docs and records acceptance via POST /users/consent/accept.
  • New client-side service src/services/consent.ts (no server actions).

Risks / breaking changes

  • Cross-repo: pairs with peanut-api hot-fix: poland guest link claim issue #1209. Either merge order is safe — the echoes are optional fields/headers the old backend ignores, and a 404 on consent-status just logs (fail-open, app never locks).
  • All pre-ledger users see the re-consent modal once after the backend ships — by design (nobody has provable consent; accepting backfills the ledger). If product wants a quieter rollout, gate the modal mount.
  • The modal is deliberately unclosable until accepted (ToS §17); a failed status fetch keeps the app usable.

QA

  1. pnpm dev → predev regenerates src/constants/legal-versions.generated.ts (7 docs).
  2. Fresh signup → backend rows for terms + privacy @ 2026-07-15 with the generated hashes.
  3. Card apply (US + international) → regional doc-set rows, surface='card-apply'.
  4. Pre-ledger user (or backdated row) → modal on app load; doc links open (web: new tab, native: in-app browser); accept → surface='re-consent' rows; modal never returns.
  5. Backend down → app loads normally, console warning only.

🤖 Generated with Claude Code

Design notes / accepted trade-offs

  • Generated constants are committed so CI and static analysis see them without running the generator; predev/prebuild regenerate, and the output is byte-stable under prettier by construction.
  • Version skew self-heals: if content ships before this app redeploys, a re-consent acceptance records the older displayed version (backend drift-warns), the modal reappears next session, and the next FE deploy trues it up.
  • Reveals, not blocking: legal-doc links now live in two maps (CardTermsScreen's LINKS, absolute URLs for native, and ReConsentModal's DOC_LABELS, relative for DocsLink). Unifying them into one legal-links const is a small follow-up.

Summary by CodeRabbit

  • New Features
    • Added prompts for existing users to review and accept updated legal documents before continuing.
    • Added clear document links, acceptance checkbox, loading states, and error handling.
    • Legal document consent is now recorded during account registration and card applications.
  • Bug Fixes
    • Ensured consent reflects the user’s residency when applying for a card.
  • Analytics
    • Added tracking for re-consent modal display and completion actions.

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 9:49pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d21bbe70-0e71-41d0-a1b2-7245cb3bb3a9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds generated legal-document versions and consent APIs, sends consent records during signup and card applications, and introduces a session-scoped re-consent modal with analytics and acceptance handling.

Changes

Consent ledger flow

Layer / File(s) Summary
Legal version generation
package.json, scripts/generate-legal-versions.mjs
Development and build hooks generate legal document versions, hashes, and TypeScript exports from legal content.
Consent service contracts
src/services/consent.ts
Defines consent data types, signup and card document selections, and status and acceptance API calls.
Signup and card consent submission
src/hooks/useZeroDev.ts, src/app/(mobile-ui)/card/page.tsx, src/services/rain.ts
Passkey registration and card applications include accepted legal-document records in their requests.
Re-consent modal flow
src/components/Global/ReConsentModal/index.tsx, src/app/(mobile-ui)/layout.tsx, src/constants/analytics.consts.ts
Checks consent status, displays required documents, records acceptance, and emits re-consent modal analytics.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ReConsentModal
  participant consentApi
  participant PostHog
  User->>ReConsentModal: Authenticate
  ReConsentModal->>consentApi: getStatus()
  consentApi-->>ReConsentModal: Consent status
  ReConsentModal->>PostHog: Record MODAL_SHOWN
  User->>ReConsentModal: Accept documents
  ReConsentModal->>consentApi: accept(documents)
  consentApi-->>ReConsentModal: Acceptance recorded
  ReConsentModal->>PostHog: Record MODAL_DISMISSED
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: hugo0

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: consent-ledger echo and re-consent flow for ToS v1 phase 2.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tos-v1-consent-client

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

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6233.95 → 6251.67 (+17.72)
Findings: +4 net (+45 new, -41 resolved)

🆕 New findings (45)

  • critical complexity — src/app/(mobile-ui)/card/page.tsx — CC 107, MI 56.8, SLOC 422
  • critical complexity — src/services/rain.ts — CC 66, MI 60.12, SLOC 260
  • high hotspot — src/app/(mobile-ui)/card/page.tsx — 44 commits, +1189/-491 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 — 35 commits, +321/-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 32.96, SLOC 173
  • medium high-mdd — src/app/(mobile-ui)/card/page.tsx:54 — CardPage: MDD 111.7 (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:54 — CardPage: DLT 50 (calls 50 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 complexity — src/components/Global/ReConsentModal/index.tsx — CC 24, MI 60.96, SLOC 112
  • medium hotspot — src/services/rain.ts — 24 commits, +840/-95 lines since 6 months ago
  • 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:491 — CC 16 SLOC 51
  • medium react-effect-derives-state — src/app/(mobile-ui)/layout.tsx:62 — useEffect with empty deps + setState — derived state anti-pattern
  • medium react-direct-dom — src/app/(mobile-ui)/layout.tsx:76 — direct DOM: document.querySelector

…and 25 more.

✅ Resolved (41)

  • src/app/(mobile-ui)/card/page.tsx — CC 106, MI 56.91, SLOC 419
  • src/services/rain.ts — CC 64, MI 60.26, SLOC 258
  • src/app/(mobile-ui)/card/page.tsx — 43 commits, +1181/-489 lines since 6 months ago
  • src/app/(mobile-ui)/layout.tsx:36 — CC 38 SLOC 65
  • src/constants/analytics.consts.ts — 34 commits, +320/-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.03, SLOC 172
  • src/app/(mobile-ui)/card/page.tsx:53 — CardPage: MDD 110.2 (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:53 — CardPage: DLT 49 (calls 49 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/utils/general.utils.ts — 24 commits, +134/-152 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:485 — CC 16 SLOC 51
  • 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
  • src/app/(mobile-ui)/layout.tsx:36 — Layout: DLT 25 (calls 25 distinct functions — high context load)

…and 21 more.

📈 Painscore deltas (top movers)

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

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2010 ran, 0 failed, 0 skipped, 34.5s

📊 Coverage (unit)

metric %
statements 59.7%
branches 43.3%
functions 48.5%
lines 60.0%
⏱ 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.2s 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/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.3s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.2s src/utils/__tests__/url.utils.test.ts › uses the public BASE_URL in Capacitor, not the localhost WebView origin
0.2s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s 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`.

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.
@jjramirezn

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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.

A logout followed by login as a different account in the same SPA
session skipped the second user's consent-status check.
@jjramirezn

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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.

@jjramirezn

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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.

@jjramirezn

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@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: 2

🤖 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 `@package.json`:
- Around line 15-16: Update the package scripts for dev:clean, dev:fallback, and
analyze so each supported entrypoint runs scripts/generate-legal-versions.mjs
before its existing command, ensuring legal version and hash constants are
regenerated regardless of how development or analysis is started.

In `@src/components/Global/ReConsentModal/index.tsx`:
- Around line 35-48: Update the ReConsentModal effect around lastCheckedUserId,
outdatedDocs, and checked to reset consent UI state whenever the authenticated
userId changes, ensuring prior users’ modal and checkbox state is not reused.
Reset checked after a successful consent submission, and add cancellation or a
userId/token guard to the consentApi.getStatus response before calling
setOutdatedDocs so late responses cannot update state for another user.
🪄 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: b279ef57-4a5c-4541-a76e-09baf3eaf5e7

📥 Commits

Reviewing files that changed from the base of the PR and between 6c20a70 and a0bccaf.

⛔ Files ignored due to path filters (1)
  • src/constants/legal-versions.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (9)
  • package.json
  • scripts/generate-legal-versions.mjs
  • src/app/(mobile-ui)/card/page.tsx
  • src/app/(mobile-ui)/layout.tsx
  • src/components/Global/ReConsentModal/index.tsx
  • src/constants/analytics.consts.ts
  • src/hooks/useZeroDev.ts
  • src/services/consent.ts
  • src/services/rain.ts

Comment thread package.json Outdated
Comment thread src/components/Global/ReConsentModal/index.tsx
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.
@jjramirezn
jjramirezn marked this pull request as ready for review July 20, 2026 21:50
@jjramirezn
jjramirezn requested a review from Hugo0 July 20, 2026 21:50
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.

1 participant