Skip to content

feat(support): surface live verification state to Crisp agents - #2487

Open
innolope-dev wants to merge 2 commits into
mainfrom
feat/crisp-support-verification-state
Open

feat(support): surface live verification state to Crisp agents#2487
innolope-dev wants to merge 2 commits into
mainfrom
feat/crisp-support-verification-state

Conversation

@innolope-dev

@innolope-dev innolope-dev commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Problem

Support agents have no visibility into a user's live verification state. In a recent case, three agents took turns guessing what state a user was in because none of it reaches Crisp — today we only send username, email, wallet, Bridge and PostHog links, nothing about where the user is stuck.

Closes #2360.

What this does

Adds a support-facing verification snapshot to the Crisp agent sidebar (session:data). Everything is derived from the two backend read-models already on the /get-user response (capabilities, identityVerification) — no backend change, no new provider-state interpretation on the client. New fields visible to agents:

field source
identity_status identityVerification.status
email_on_file whether an email is on file (provider submission can't run without one)
verification_gates per-op gate kinds via existing deriveGate, e.g. pay:ready deposit:provide-email withdraw:blocked-rejection
failure_reason the stuck rail's id + normalized reason.code + technical details
pending_actions capabilities.nextActions as kind(purpose)

It's threaded through all three Crisp sinks: web widget (setCrispUserData), proxy iframe (useCrispProxyUrlcrisp-proxy), and native Capacitor (SupportDrawer).

Design notes

  • Sidebar only — the user's own message is never modified. An earlier revision also appended a state line to the support message text, but message:text populates the user-visible composer, which would expose internal codes/rail-ids to the user. Dropped in favour of the agent-only sidebar, which already carries the full state.
  • Logic lives in a pure, unit-tested buildSupportVerificationSummary() (reuses deriveGate / railVerdict — no duplicated state-machine logic).

Out of scope (filed separately)

  • The issue's mantecaUserId "while here" needs the backend to expose a provider-account id first (the capability model is deliberately provider-blind) — can't be done FE-only.
  • The longer-term read-only "shadow view" is a genuine feature, not part of this fix.

Testing

  • New support-verification.test.ts (5 cases: gates, stuck-rail failure reason, pending actions, graceful degrade, identity status).
  • tsc --noEmit clean, existing SupportDrawer suite green, prettier clean.

Summary by CodeRabbit

  • New Features
    • Support conversations now include identity verification status, email availability, verification requirements, failure details, and pending actions.
    • Support agents receive richer verification context in the Crisp messenger.
  • Bug Fixes
    • Verification context stays current as user details change and remains available when opening native support messaging.
  • Tests
    • Added coverage for verified, blocked/fixable, pending-action, and incomplete verification scenarios.

Support agents had no visibility into where a user was stuck in
verification, so they resorted to guessing. Derive a support-facing
snapshot from the capability + identity read-models already on the
/get-user response and push it into Crisp session:data (agent sidebar):
identity status, email-on-file, per-operation gate kinds, the stuck
rail's failure reason + technical details, and pending next-actions.

Threaded through all Crisp sinks (web widget, proxy iframe, native
Capacitor). The user's own message is never modified.

Closes #2360
@innolope-dev innolope-dev self-assigned this Jul 23, 2026
@vercel

vercel Bot commented Jul 23, 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 23, 2026 8:30am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: df909f57-7687-4e99-90f1-48a04dc8c295

📥 Commits

Reviewing files that changed from the base of the PR and between 0b2f673 and 480fc8f.

📒 Files selected for processing (7)
  • src/app/crisp-proxy/page.tsx
  • src/components/Global/SupportDrawer/index.tsx
  • src/hooks/useCrispProxyUrl.ts
  • src/hooks/useCrispUserData.ts
  • src/utils/__tests__/support-verification.test.ts
  • src/utils/crisp.ts
  • src/utils/support-verification.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/components/Global/SupportDrawer/index.tsx
  • src/app/crisp-proxy/page.tsx
  • src/hooks/useCrispProxyUrl.ts
  • src/utils/crisp.ts
  • src/utils/tests/support-verification.test.ts
  • src/utils/support-verification.ts
  • src/hooks/useCrispUserData.ts

📝 Walkthrough

Walkthrough

The PR derives live verification context, propagates it through Crisp user data and proxy URLs, and sends it to web and native Crisp support sessions.

Changes

Crisp verification context

Layer / File(s) Summary
Build verification summary
src/utils/support-verification.ts, src/utils/__tests__/support-verification.test.ts
Derives identity status, email presence, verification gates, failure details, and pending actions, with tests for populated and missing inputs.
Propagate verification data
src/hooks/useCrispUserData.ts, src/hooks/useCrispProxyUrl.ts
Adds the derived fields to Crisp user data and recomputes proxy URLs when they change.
Send Crisp session metadata
src/utils/crisp.ts, src/app/crisp-proxy/page.tsx, src/components/Global/SupportDrawer/index.tsx
Sends verification fields through web session metadata and native Crisp custom data.

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

Sequence Diagram(s)

sequenceDiagram
  participant useCrispUserData
  participant useCrispProxyUrl
  participant Crisp
  useCrispUserData->>useCrispProxyUrl: derived verification fields
  useCrispProxyUrl->>Crisp: session_data metadata
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: jjramirezn

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 clearly matches the main change: exposing live verification state to Crisp support agents.
Linked Issues check ✅ Passed The PR covers the core support verification snapshot requirements: gate/rail, failure reason, email-on-file, identity status, and pending actions.
Out of Scope Changes check ✅ Passed All changes stay within Crisp support-state plumbing and tests, with no unrelated feature work visible.
✨ 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/crisp-support-verification-state

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 23, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6247.08 → 6256.4 (+9.32)
Findings: +3 net (+22 new, -19 resolved)

🆕 New findings (22)

  • critical complexity — src/components/Global/SupportDrawer/index.tsx — CC 58, MI 60.81, SLOC 147
  • high complexity — src/app/crisp-proxy/page.tsx — CC 48, MI 60.89, SLOC 120
  • high complexity — src/hooks/useCrispProxyUrl.ts — CC 35, MI 55.44, SLOC 36
  • high complexity — src/utils/crisp.ts — CC 35, MI 59.55, SLOC 70
  • high method-complexity — src/hooks/useCrispProxyUrl.ts:17 — CC 33 SLOC 32
  • medium high-mdd — src/components/Global/SupportDrawer/index.tsx:15 — SupportDrawer: MDD 57.9 (uses across many lines from declarations)
  • medium high-mdd — src/app/crisp-proxy/page.tsx:21 — : MDD 37.8 (uses across many lines from declarations)
  • medium high-mdd — src/app/crisp-proxy/page.tsx:18 — CrispProxyContent: MDD 36.9 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/useCrispProxyUrl.ts:16 — useCrispProxyUrl: MDD 36.0 (uses across many lines from declarations)
  • medium high-mdd — src/utils/crisp.ts:15 — setCrispUserData: MDD 29.3 (uses across many lines from declarations)
  • medium method-complexity — src/app/crisp-proxy/page.tsx:64 — CC 23 SLOC 34
  • medium method-complexity — src/utils/crisp.ts:15 — setCrispUserData CC 23 SLOC 29
  • medium method-complexity — src/components/Global/SupportDrawer/index.tsx:65 — CC 18 SLOC 39
  • medium complexity — src/utils/support-verification.ts — CC 15, MI 64.46, SLOC 60
  • medium complexity — src/hooks/useCrispUserData.ts — CC 13, MI 59.75, SLOC 40
  • medium react-effect-derives-state — src/components/Global/SupportDrawer/index.tsx:132 — useEffect with empty deps + setState — derived state anti-pattern
  • low high-mdd — src/hooks/useCrispProxyUrl.ts:17 — : MDD 19.1 (uses across many lines from declarations)
  • low high-mdd — src/hooks/useCrispUserData.ts:37 — useCrispUserData: MDD 15.5 (uses across many lines from declarations)
  • low high-mdd — src/hooks/useCrispUserData.ts:40 — : MDD 15.5 (uses across many lines from declarations)
  • low high-mdd — src/components/Global/SupportDrawer/index.tsx:132 — : MDD 11.5 (uses across many lines from declarations)

…and 2 more.

✅ Resolved (19)

  • src/components/Global/SupportDrawer/index.tsx — CC 51, MI 62.6, SLOC 129
  • src/app/crisp-proxy/page.tsx — CC 42, MI 61.18, SLOC 120
  • src/components/Global/SupportDrawer/index.tsx:15 — SupportDrawer: MDD 52.7 (uses across many lines from declarations)
  • src/app/crisp-proxy/page.tsx:21 — : MDD 39.6 (uses across many lines from declarations)
  • src/app/crisp-proxy/page.tsx:18 — CrispProxyContent: MDD 38.4 (uses across many lines from declarations)
  • src/hooks/useCrispProxyUrl.ts:16 — useCrispProxyUrl: MDD 29.3 (uses across many lines from declarations)
  • src/hooks/useCrispProxyUrl.ts — CC 28, MI 57.98, SLOC 30
  • src/utils/crisp.ts — CC 28, MI 61.1, SLOC 64
  • src/hooks/useCrispProxyUrl.ts:17 — CC 26 SLOC 26
  • src/utils/crisp.ts:15 — setCrispUserData: MDD 23.3 (uses across many lines from declarations)
  • src/app/crisp-proxy/page.tsx:64 — CC 17 SLOC 34
  • src/utils/crisp.ts:15 — setCrispUserData CC 16 SLOC 23
  • src/hooks/useCrispUserData.ts — CC 12, MI 62.9, SLOC 31
  • src/components/Global/SupportDrawer/index.tsx:120 — useEffect with empty deps + setState — derived state anti-pattern
  • src/hooks/useCrispProxyUrl.ts:17 — : MDD 16.7 (uses across many lines from declarations)
  • src/hooks/useCrispUserData.ts:29 — useCrispUserData: MDD 12.8 (uses across many lines from declarations)
  • src/hooks/useCrispUserData.ts:32 — : MDD 12.8 (uses across many lines from declarations)
  • src/components/Global/SupportDrawer/index.tsx:120 — : MDD 11.5 (uses across many lines from declarations)
  • src/app/crisp-proxy/page.tsx:212 — CrispProxyPage: exported fn missing return type annotation

📈 Painscore deltas (top movers)

File Before After Δ
src/utils/support-verification.ts 0.0 5.1 +5.1
src/hooks/useCrispProxyUrl.ts 9.7 10.9 +1.2
src/hooks/useCrispUserData.ts 7.0 8.0 +1.0
src/utils/crisp.ts 7.6 8.3 +0.8
src/components/Global/SupportDrawer/index.tsx 10.3 11.0 +0.7

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2068 ran, 0 failed, 0 skipped, 26.9s

📊 Coverage (unit)

metric %
statements 60.2%
branches 44.0%
functions 49.1%
lines 60.5%
⏱ 10 slowest test cases
time test
2.7s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.9s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
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 › 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.2s 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.1s src/utils/__tests__/demo-balance.test.ts › auto-refills a stored balance that has no timestamp (legacy install)
0.1s src/utils/__tests__/demo-balance.test.ts › auto-refills a wallet older than the TTL on cold start
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@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 `@src/components/Global/SupportDrawer/index.tsx`:
- Around line 84-99: Update the CapacitorCrisp metadata handling in the support
drawer so verification_gates, failure_reason, and pending_actions are always
written, using an empty string when their corresponding userData values are
absent. Preserve the existing values when present and align this native behavior
with the web/proxy paths.

In `@src/utils/support-verification.ts`:
- Around line 41-42: Update the gate summary construction around
SUMMARY_OPERATIONS, deriveGate, and gates to include the rail selected by the
same gate-resolution ordering alongside each operation and kind. Preserve
existing formatting for other fields, ensure pending and waiting-on-provider
gates expose the selected rail even without a failureReason, and add coverage
for this stuck-gate state.
🪄 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: 5b8f84b8-8bb1-4adc-a88a-1653f31c537d

📥 Commits

Reviewing files that changed from the base of the PR and between 5fd5969 and 0b2f673.

📒 Files selected for processing (7)
  • src/app/crisp-proxy/page.tsx
  • src/components/Global/SupportDrawer/index.tsx
  • src/hooks/useCrispProxyUrl.ts
  • src/hooks/useCrispUserData.ts
  • src/utils/__tests__/support-verification.test.ts
  • src/utils/crisp.ts
  • src/utils/support-verification.ts

Comment thread src/components/Global/SupportDrawer/index.tsx Outdated
Comment thread src/utils/support-verification.ts Outdated
…tive fields

Address CodeRabbit review on #2487:
- add verification_rails (each non-enabled rail as id:status(code)) so agents
  can identify WHICH rail is stuck even for pending/waiting gates, where there
  is no failure reason to fall back on.
- native Capacitor path now always writes the verification fields (empty string
  when absent), matching the web/proxy sinks so a prior user's values can't
  linger on the device-local Crisp session.

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

Reviewed — SAFE. Approving.

No raw PII crosses to Crisp — the payload is status enums, gate tokens, backend-controlled failure/reason strings, and a yes/no email_on_file boolean (not the address itself). The surfaced set is minimized and gated. Gives support the verification state they need without leaking user data.

No blocking issues.

@kushagrasarathe

Copy link
Copy Markdown
Contributor

@innolope-dev some merge conflicts

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.

Support can't see a user's live verification state, so they end up guessing

2 participants