From 5769cc9081c8aa06df9a44b74e7a051d81d91f6a Mon Sep 17 00:00:00 2001 From: Josue19-08 Date: Wed, 9 Sep 2026 19:37:55 -0600 Subject: [PATCH] Add human-readable risk-level UI instead of raw enum display Judges see the raw numeric RiskLevel enum and, when matches exist, would otherwise need the pattern's internal category slug to understand what was flagged. Map each level to a label/color/icon and render matched categories and evidence snippets in plain language instead. Co-Authored-By: Claude Sonnet 5 --- apps/web/src/App.tsx | 17 ++------------ apps/web/src/RiskResult.tsx | 27 ++++++++++++++++++++++ apps/web/src/riskPresentation.ts | 39 ++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 apps/web/src/RiskResult.tsx create mode 100644 apps/web/src/riskPresentation.ts diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 8f54c0b..1db4193 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import type { RiskAssessment } from "@custos/core"; -import { requiresFriction, requiresHardBlock } from "@custos/core"; import { analyzeSendIntent } from "./compositionRoot.js"; +import { RiskResult } from "./RiskResult.js"; export function App() { const [address, setAddress] = useState(""); @@ -62,20 +62,7 @@ export function App() { {busy ? "Analyzing on-device..." : "Check before sending"} - {assessment && ( -
- Risk level: {assessment.level} -

{assessment.summary}

- {requiresHardBlock(assessment.level) &&

This send would be blocked — WDK signing must not proceed.

} -
- )} + {assessment && } ); } diff --git a/apps/web/src/RiskResult.tsx b/apps/web/src/RiskResult.tsx new file mode 100644 index 0000000..7267653 --- /dev/null +++ b/apps/web/src/RiskResult.tsx @@ -0,0 +1,27 @@ +import type { RiskAssessment } from "@custos/core"; +import { requiresHardBlock } from "@custos/core"; +import { describeRiskLevel, describeScamCategory } from "./riskPresentation.js"; + +export function RiskResult({ assessment }: { assessment: RiskAssessment }) { + const { label, color, icon } = describeRiskLevel(assessment.level); + + return ( +
+ + {icon} {label} + +

{assessment.summary}

+ {assessment.matches.length > 0 && ( +
    + {assessment.matches.map((match, i) => ( +
  • + {describeScamCategory(match.pattern.category)} + {match.evidenceSnippet ? ` — ${match.evidenceSnippet}` : ""} +
  • + ))} +
+ )} + {requiresHardBlock(assessment.level) &&

This send would be blocked — WDK signing must not proceed.

} +
+ ); +} diff --git a/apps/web/src/riskPresentation.ts b/apps/web/src/riskPresentation.ts new file mode 100644 index 0000000..5717754 --- /dev/null +++ b/apps/web/src/riskPresentation.ts @@ -0,0 +1,39 @@ +import { RiskLevel } from "@custos/core"; +import type { ScamCategory } from "@custos/core"; + +export interface RiskLevelPresentation { + readonly label: string; + readonly color: string; + readonly icon: string; +} + +const RISK_LEVEL_PRESENTATION: Record = { + [RiskLevel.None]: { label: "No known risk", color: "#27ae60", icon: "✅" }, + [RiskLevel.Low]: { label: "Low — minor signals detected", color: "#16a085", icon: "ℹ️" }, + [RiskLevel.Elevated]: { label: "Elevated — review before sending", color: "#e67e22", icon: "⚠️" }, + [RiskLevel.High]: { label: "High — strong scam signals", color: "#d35400", icon: "🔶" }, + [RiskLevel.Critical]: { label: "Critical — send blocked", color: "#c0392b", icon: "🛑" }, +}; + +export function describeRiskLevel(level: RiskLevel): RiskLevelPresentation { + return RISK_LEVEL_PRESENTATION[level]; +} + +const SCAM_CATEGORY_LABEL: Record = { + "pig-butchering": "Pig-butchering scam", + "fake-support": "Fake support impersonation", + "recovery-scam": "Fund recovery scam", + "romance-scam": "Romance scam", + "fake-investment-platform": "Fake investment platform", + "urgency-pressure": "Urgency/pressure tactics", + "address-poisoning": "Lookalike address (poisoning attempt)", + "task-scam": "Task/job scam", + "impersonation-authority": "Authority impersonation", + "airdrop-claim-fee": "Fake airdrop claim fee", + "otc-escrow-scam": "OTC escrow scam", + "phishing-approval-drain": "Phishing / approval drain", +}; + +export function describeScamCategory(category: ScamCategory): string { + return SCAM_CATEGORY_LABEL[category]; +}