@decentrys/ui-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/dist/browser/decentrys-ui.js +1733 -0
  4. package/dist/browser/decentrys-ui.mjs +1681 -0
  5. package/dist/browser/react-dom-global.d.ts +6 -0
  6. package/dist/browser/react-dom-global.d.ts.map +1 -0
  7. package/dist/browser/react-dom-global.js +18 -0
  8. package/dist/browser/react-dom-global.js.map +1 -0
  9. package/dist/browser/react-global.d.ts +25 -0
  10. package/dist/browser/react-global.d.ts.map +1 -0
  11. package/dist/browser/react-global.js +39 -0
  12. package/dist/browser/react-global.js.map +1 -0
  13. package/dist/browser/react-jsx-runtime.d.ts +17 -0
  14. package/dist/browser/react-jsx-runtime.d.ts.map +1 -0
  15. package/dist/browser/react-jsx-runtime.js +27 -0
  16. package/dist/browser/react-jsx-runtime.js.map +1 -0
  17. package/dist/components/AddressRiskBadge.d.ts +39 -0
  18. package/dist/components/AddressRiskBadge.d.ts.map +1 -0
  19. package/dist/components/AddressRiskBadge.js +26 -0
  20. package/dist/components/AddressRiskBadge.js.map +1 -0
  21. package/dist/components/ApprovalWarning.d.ts +29 -0
  22. package/dist/components/ApprovalWarning.d.ts.map +1 -0
  23. package/dist/components/ApprovalWarning.js +18 -0
  24. package/dist/components/ApprovalWarning.js.map +1 -0
  25. package/dist/components/ContractFacts.d.ts +27 -0
  26. package/dist/components/ContractFacts.d.ts.map +1 -0
  27. package/dist/components/ContractFacts.js +15 -0
  28. package/dist/components/ContractFacts.js.map +1 -0
  29. package/dist/components/RiskDetailsModal.d.ts +35 -0
  30. package/dist/components/RiskDetailsModal.d.ts.map +1 -0
  31. package/dist/components/RiskDetailsModal.js +74 -0
  32. package/dist/components/RiskDetailsModal.js.map +1 -0
  33. package/dist/components/ThreatSignalList.d.ts +28 -0
  34. package/dist/components/ThreatSignalList.d.ts.map +1 -0
  35. package/dist/components/ThreatSignalList.js +28 -0
  36. package/dist/components/ThreatSignalList.js.map +1 -0
  37. package/dist/components/TokenSecurityPanel.d.ts +28 -0
  38. package/dist/components/TokenSecurityPanel.d.ts.map +1 -0
  39. package/dist/components/TokenSecurityPanel.js +26 -0
  40. package/dist/components/TokenSecurityPanel.js.map +1 -0
  41. package/dist/components/TransactionRiskBanner.d.ts +36 -0
  42. package/dist/components/TransactionRiskBanner.d.ts.map +1 -0
  43. package/dist/components/TransactionRiskBanner.js +20 -0
  44. package/dist/components/TransactionRiskBanner.js.map +1 -0
  45. package/dist/context.d.ts +51 -0
  46. package/dist/context.d.ts.map +1 -0
  47. package/dist/context.js +47 -0
  48. package/dist/context.js.map +1 -0
  49. package/dist/copy.d.ts +72 -0
  50. package/dist/copy.d.ts.map +1 -0
  51. package/dist/copy.js +77 -0
  52. package/dist/copy.js.map +1 -0
  53. package/dist/index.d.ts +57 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +107 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/presentation.d.ts +316 -0
  58. package/dist/presentation.d.ts.map +1 -0
  59. package/dist/presentation.js +533 -0
  60. package/dist/presentation.js.map +1 -0
  61. package/dist/primitives.d.ts +139 -0
  62. package/dist/primitives.d.ts.map +1 -0
  63. package/dist/primitives.js +150 -0
  64. package/dist/primitives.js.map +1 -0
  65. package/dist/styles.d.ts +15 -0
  66. package/dist/styles.d.ts.map +1 -0
  67. package/dist/styles.js +285 -0
  68. package/dist/styles.js.map +1 -0
  69. package/dist/theme.d.ts +52 -0
  70. package/dist/theme.d.ts.map +1 -0
  71. package/dist/theme.js +90 -0
  72. package/dist/theme.js.map +1 -0
  73. package/package.json +70 -0
  74. package/src/browser/react-dom-global.ts +20 -0
  75. package/src/browser/react-global.ts +42 -0
  76. package/src/browser/react-jsx-runtime.ts +23 -0
  77. package/src/components/AddressRiskBadge.tsx +94 -0
  78. package/src/components/ApprovalWarning.tsx +144 -0
  79. package/src/components/ContractFacts.tsx +119 -0
  80. package/src/components/RiskDetailsModal.tsx +229 -0
  81. package/src/components/ThreatSignalList.tsx +76 -0
  82. package/src/components/TokenSecurityPanel.tsx +168 -0
  83. package/src/components/TransactionRiskBanner.tsx +123 -0
  84. package/src/context.tsx +89 -0
  85. package/src/copy.test.ts +214 -0
  86. package/src/copy.ts +151 -0
  87. package/src/index.ts +84 -0
  88. package/src/presentation.test.ts +521 -0
  89. package/src/presentation.ts +846 -0
  90. package/src/primitives.tsx +384 -0
  91. package/src/styles.ts +281 -0
  92. package/src/theme.test.ts +105 -0
  93. package/src/theme.ts +117 -0
@@ -0,0 +1,1733 @@
1
+ "use strict";
2
+ var DecentrysUI = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ AddressRiskBadge: () => AddressRiskBadge,
25
+ ApprovalWarning: () => ApprovalWarning,
26
+ Chip: () => Chip,
27
+ ContractFacts: () => ContractFacts,
28
+ DECENTRYS_UI_CSS: () => DECENTRYS_UI_CSS,
29
+ DEFAULT_COPY: () => DEFAULT_COPY,
30
+ DecentrysUiProvider: () => DecentrysUiProvider,
31
+ DecentrysUiStyles: () => DecentrysUiStyles,
32
+ EmptyNote: () => EmptyNote,
33
+ LevelBadge: () => LevelBadge,
34
+ MIN_RAISING_CONFIDENCE: () => MIN_RAISING_CONFIDENCE,
35
+ Mono: () => Mono,
36
+ RISK_LEVEL_LABEL: () => RISK_LEVEL_LABEL,
37
+ RiskDetailsModal: () => RiskDetailsModal,
38
+ ScreenReaderText: () => ScreenReaderText,
39
+ Section: () => Section,
40
+ THEME_TOKENS: () => THEME_TOKENS,
41
+ ThreatSignalList: () => ThreatSignalList,
42
+ TokenSecurityPanel: () => TokenSecurityPanel,
43
+ TransactionRiskBanner: () => TransactionRiskBanner,
44
+ approvalPresentation: () => approvalPresentation,
45
+ assessmentSections: () => assessmentSections,
46
+ availabilityPresentation: () => availabilityPresentation,
47
+ capabilityPresentation: () => capabilityPresentation,
48
+ componentRows: () => componentRows,
49
+ cx: () => cx,
50
+ decisionPresentation: () => decisionPresentation,
51
+ evidencePresentation: () => evidencePresentation,
52
+ factPresentation: () => factPresentation,
53
+ formatConfidence: () => formatConfidence,
54
+ formatTimestamp: () => formatTimestamp,
55
+ formatTokenAmount: () => formatTokenAmount,
56
+ historyPresentation: () => historyPresentation,
57
+ hopPresentation: () => hopPresentation,
58
+ isUnlimitedAllowance: () => isUnlimitedAllowance,
59
+ kindPresentation: () => kindPresentation,
60
+ levelPresentation: () => levelPresentation,
61
+ levelRank: () => levelRank,
62
+ plural: () => plural,
63
+ resolveCopy: () => resolveCopy,
64
+ shortenAddress: () => shortenAddress,
65
+ signalPresentation: () => signalPresentation,
66
+ themeStyle: () => themeStyle,
67
+ toneBgVar: () => toneBgVar,
68
+ toneVar: () => toneVar,
69
+ unknownPresentation: () => unknownPresentation,
70
+ useUiConfig: () => useUiConfig
71
+ });
72
+
73
+ // src/browser/react-global.ts
74
+ function resolveReact() {
75
+ const candidate = globalThis.React;
76
+ if (!candidate || typeof candidate.createElement !== "function") {
77
+ throw new Error(
78
+ "DecentrysUI: the script-tag build expects React on the page already (window.React, and window.ReactDOM for the details modal). Load React first, or use the ESM build / the npm package if you have a bundler."
79
+ );
80
+ }
81
+ return candidate;
82
+ }
83
+ var React = resolveReact();
84
+ var react_global_default = React;
85
+ var createContext = React.createContext;
86
+ var useContext = React.useContext;
87
+ var useState = React.useState;
88
+ var useEffect = React.useEffect;
89
+ var useId = React.useId;
90
+ var useRef = React.useRef;
91
+ var createElement = React.createElement;
92
+ var Fragment = React.Fragment;
93
+
94
+ // src/styles.ts
95
+ var DECENTRYS_UI_CSS = `
96
+ .dcy {
97
+ --dcy-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
98
+ --dcy-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
99
+ --dcy-radius: 8px;
100
+ --dcy-gap: 12px;
101
+
102
+ --dcy-surface: #ffffff;
103
+ --dcy-surface-raised: #f5f6f8;
104
+ --dcy-border: #dde1e7;
105
+ --dcy-text: #14171c;
106
+ --dcy-text-muted: #545d6b;
107
+ --dcy-text-faint: #737d8c;
108
+ --dcy-focus: #2f6fed;
109
+
110
+ --dcy-tone-neutral: #545d6b;
111
+ --dcy-tone-neutral-bg: #f2f4f6;
112
+ --dcy-tone-info: #2f5f96;
113
+ --dcy-tone-info-bg: #eef3f9;
114
+ --dcy-tone-caution: #7d6113;
115
+ --dcy-tone-caution-bg: #faf4e4;
116
+ --dcy-tone-elevated: #97500f;
117
+ --dcy-tone-elevated-bg: #fbefe3;
118
+ --dcy-tone-high: #a83c18;
119
+ --dcy-tone-high-bg: #fbece6;
120
+ --dcy-tone-critical: #98211d;
121
+ --dcy-tone-critical-bg: #fbe9e9;
122
+ --dcy-tone-fact: #6a7382;
123
+ --dcy-tone-fact-bg: transparent;
124
+ --dcy-tone-capability: #554d8c;
125
+ --dcy-tone-capability-bg: #f2f1fa;
126
+ --dcy-tone-unknown: #77808f;
127
+ --dcy-tone-unknown-bg: transparent;
128
+ --dcy-tone-inactive: #8d97a4;
129
+ --dcy-tone-inactive-bg: transparent;
130
+
131
+ font-family: var(--dcy-font);
132
+ font-size: 14px;
133
+ line-height: 1.5;
134
+ color: var(--dcy-text);
135
+ box-sizing: border-box;
136
+ }
137
+ .dcy *, .dcy *::before, .dcy *::after { box-sizing: inherit; }
138
+
139
+ @media (prefers-color-scheme: dark) {
140
+ .dcy {
141
+ --dcy-surface: #14171c;
142
+ --dcy-surface-raised: #1c2027;
143
+ --dcy-border: #2c323b;
144
+ --dcy-text: #e8ebef;
145
+ --dcy-text-muted: #a4adba;
146
+ --dcy-text-faint: #808a98;
147
+ --dcy-focus: #7aa5ff;
148
+
149
+ --dcy-tone-neutral: #a4adba;
150
+ --dcy-tone-neutral-bg: #1e232a;
151
+ --dcy-tone-info: #86b0e6;
152
+ --dcy-tone-info-bg: #17202b;
153
+ --dcy-tone-caution: #d8b463;
154
+ --dcy-tone-caution-bg: #241f14;
155
+ --dcy-tone-elevated: #e0a065;
156
+ --dcy-tone-elevated-bg: #261c13;
157
+ --dcy-tone-high: #eb8c68;
158
+ --dcy-tone-high-bg: #271812;
159
+ --dcy-tone-critical: #ef7d78;
160
+ --dcy-tone-critical-bg: #291414;
161
+ --dcy-tone-fact: #97a1ae;
162
+ --dcy-tone-capability: #a79fe0;
163
+ --dcy-tone-capability-bg: #1d1b2a;
164
+ --dcy-tone-unknown: #8b95a3;
165
+ --dcy-tone-inactive: #79828f;
166
+ }
167
+ }
168
+
169
+
170
+ /*
171
+ * Tone resolution happens here, in the stylesheet, rather than in inline
172
+ * styles on the elements. An inline colour would beat any rule an integrator
173
+ * wrote against these components without \`!important\`, which is not a
174
+ * position to put somebody in who only wanted to match their own palette.
175
+ */
176
+ [data-dcy-tone="neutral"] { --dcy-tone: var(--dcy-tone-neutral); --dcy-tone-bg: var(--dcy-tone-neutral-bg); }
177
+ [data-dcy-tone="info"] { --dcy-tone: var(--dcy-tone-info); --dcy-tone-bg: var(--dcy-tone-info-bg); }
178
+ [data-dcy-tone="caution"] { --dcy-tone: var(--dcy-tone-caution); --dcy-tone-bg: var(--dcy-tone-caution-bg); }
179
+ [data-dcy-tone="elevated"] { --dcy-tone: var(--dcy-tone-elevated); --dcy-tone-bg: var(--dcy-tone-elevated-bg); }
180
+ [data-dcy-tone="high"] { --dcy-tone: var(--dcy-tone-high); --dcy-tone-bg: var(--dcy-tone-high-bg); }
181
+ [data-dcy-tone="critical"] { --dcy-tone: var(--dcy-tone-critical); --dcy-tone-bg: var(--dcy-tone-critical-bg); }
182
+ [data-dcy-tone="fact"] { --dcy-tone: var(--dcy-tone-fact); --dcy-tone-bg: var(--dcy-tone-fact-bg); }
183
+ [data-dcy-tone="capability"] { --dcy-tone: var(--dcy-tone-capability); --dcy-tone-bg: var(--dcy-tone-capability-bg); }
184
+ [data-dcy-tone="unknown"] { --dcy-tone: var(--dcy-tone-unknown); --dcy-tone-bg: var(--dcy-tone-unknown-bg); }
185
+ [data-dcy-tone="inactive"] { --dcy-tone: var(--dcy-tone-inactive); --dcy-tone-bg: var(--dcy-tone-inactive-bg); }
186
+
187
+ [data-dcy-rule="solid"] { border-left-style: solid; }
188
+ [data-dcy-rule="dashed"] { border-left-style: dashed; }
189
+ [data-dcy-rule="dotted"] { border-left-style: dotted; }
190
+
191
+ .dcy-row[data-dcy-kind="signal"] { border-left-width: 4px; }
192
+
193
+ .dcy :focus-visible {
194
+ outline: 2px solid var(--dcy-focus);
195
+ outline-offset: 2px;
196
+ border-radius: 4px;
197
+ }
198
+
199
+ .dcy-card {
200
+ border: 1px solid var(--dcy-border);
201
+ border-radius: var(--dcy-radius);
202
+ background: var(--dcy-surface);
203
+ padding: var(--dcy-gap);
204
+ display: flex;
205
+ flex-direction: column;
206
+ gap: var(--dcy-gap);
207
+ }
208
+
209
+ .dcy-head { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
210
+ .dcy-head-main { flex: 1 1 14rem; min-width: 0; }
211
+ .dcy-title { margin: 0; font-size: 14px; font-weight: 600; }
212
+ .dcy-meaning { margin: 4px 0 0; color: var(--dcy-text-muted); font-size: 13px; }
213
+
214
+ .dcy-badge {
215
+ display: inline-flex;
216
+ align-items: center;
217
+ gap: 6px;
218
+ padding: 2px 8px;
219
+ border-radius: 999px;
220
+ border: 1px solid currentColor;
221
+ font-size: 12px;
222
+ font-weight: 600;
223
+ line-height: 1.6;
224
+ white-space: nowrap;
225
+ color: var(--dcy-tone, var(--dcy-text-muted));
226
+ background: var(--dcy-tone-bg, transparent);
227
+ }
228
+ .dcy-glyph {
229
+ font-family: var(--dcy-font-mono);
230
+ font-weight: 700;
231
+ letter-spacing: -0.05em;
232
+ }
233
+
234
+ .dcy-chip {
235
+ display: inline-block;
236
+ padding: 1px 6px;
237
+ border-radius: 4px;
238
+ border: 1px solid var(--dcy-border);
239
+ background: var(--dcy-surface-raised);
240
+ color: var(--dcy-text-muted);
241
+ font-size: 11px;
242
+ font-weight: 600;
243
+ text-transform: uppercase;
244
+ letter-spacing: 0.04em;
245
+ white-space: nowrap;
246
+ }
247
+
248
+ .dcy-section { display: flex; flex-direction: column; gap: 8px; }
249
+ .dcy-section-title {
250
+ margin: 0;
251
+ font-size: 11px;
252
+ font-weight: 700;
253
+ text-transform: uppercase;
254
+ letter-spacing: 0.06em;
255
+ color: var(--dcy-text-faint);
256
+ }
257
+ .dcy-note { margin: 0; font-size: 12px; color: var(--dcy-text-muted); }
258
+ .dcy-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
259
+
260
+ /*
261
+ * Kind rows.
262
+ *
263
+ * The left rule's *style* and the row's text label are what separate a fact
264
+ * from a threat signal; the colour is the third carrier, not the only one.
265
+ */
266
+ .dcy-row {
267
+ display: flex;
268
+ gap: 8px;
269
+ padding: 6px 0 6px 10px;
270
+ border-left-style: solid;
271
+ border-left-width: 2px;
272
+ border-left-color: var(--dcy-tone, var(--dcy-tone-fact));
273
+ }
274
+ .dcy-row-body { min-width: 0; flex: 1 1 auto; }
275
+ .dcy-row-statement { margin: 0; }
276
+ .dcy-row-strong { font-weight: 600; }
277
+ .dcy-row-meta {
278
+ margin: 3px 0 0;
279
+ font-size: 12px;
280
+ color: var(--dcy-text-muted);
281
+ display: flex;
282
+ flex-wrap: wrap;
283
+ gap: 4px 10px;
284
+ }
285
+ .dcy-mono { font-family: var(--dcy-font-mono); font-size: 12px; word-break: break-all; }
286
+
287
+ .dcy-signal {
288
+ border: 1px solid var(--dcy-border);
289
+ border-left-width: 4px;
290
+ border-left-color: var(--dcy-tone, var(--dcy-border));
291
+ border-radius: var(--dcy-radius);
292
+ padding: 10px;
293
+ display: flex;
294
+ flex-direction: column;
295
+ gap: 6px;
296
+ }
297
+ .dcy-signal-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
298
+
299
+ .dcy-kv { display: grid; grid-template-columns: minmax(6rem, auto) 1fr; gap: 4px 12px; font-size: 13px; }
300
+ .dcy-kv dt { color: var(--dcy-text-muted); }
301
+ .dcy-kv dd { margin: 0; }
302
+
303
+ .dcy-btn {
304
+ font: inherit;
305
+ font-size: 13px;
306
+ font-weight: 600;
307
+ color: var(--dcy-text);
308
+ background: var(--dcy-surface-raised);
309
+ border: 1px solid var(--dcy-border);
310
+ border-radius: 6px;
311
+ padding: 5px 12px;
312
+ cursor: pointer;
313
+ }
314
+ .dcy-btn:hover { background: var(--dcy-border); }
315
+ .dcy-btn-quiet { background: transparent; border-color: transparent; text-decoration: underline; padding-left: 0; }
316
+
317
+ .dcy-backdrop {
318
+ position: fixed;
319
+ inset: 0;
320
+ background: rgba(8, 10, 14, 0.55);
321
+ display: flex;
322
+ align-items: flex-start;
323
+ justify-content: center;
324
+ padding: 24px 16px;
325
+ overflow: auto;
326
+ z-index: 2147483000;
327
+ }
328
+ .dcy-modal {
329
+ width: min(40rem, 100%);
330
+ max-height: calc(100vh - 48px);
331
+ overflow: auto;
332
+ background: var(--dcy-surface);
333
+ border: 1px solid var(--dcy-border);
334
+ border-radius: var(--dcy-radius);
335
+ padding: 16px;
336
+ display: flex;
337
+ flex-direction: column;
338
+ gap: var(--dcy-gap);
339
+ }
340
+ .dcy-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
341
+
342
+ .dcy-inline {
343
+ display: inline-flex;
344
+ align-items: center;
345
+ gap: 6px;
346
+ font-family: var(--dcy-font);
347
+ font-size: 12px;
348
+ }
349
+ .dcy-sr {
350
+ position: absolute;
351
+ width: 1px;
352
+ height: 1px;
353
+ padding: 0;
354
+ margin: -1px;
355
+ overflow: hidden;
356
+ clip: rect(0 0 0 0);
357
+ clip-path: inset(50%);
358
+ white-space: nowrap;
359
+ border: 0;
360
+ }
361
+ .dcy-hr { border: none; border-top: 1px solid var(--dcy-border); margin: 0; }
362
+ `;
363
+
364
+ // src/copy.ts
365
+ var DEFAULT_COPY = {
366
+ factsHeading: "Observed facts",
367
+ capabilitiesHeading: "What this contract can do",
368
+ signalsHeading: "Threat signals",
369
+ unknownsHeading: "Not known",
370
+ evidenceHeading: "Evidence",
371
+ componentsHeading: "Score components",
372
+ historyHeading: "History",
373
+ demotedHeading: "Reported as facts, not as risk",
374
+ factLabel: "Fact",
375
+ capabilityLabel: "Capability",
376
+ signalLabel: "Threat signal",
377
+ unknownLabel: "Unknown",
378
+ notAnAssurance: "An absence of findings is an absence of findings. It is not an assurance about this subject.",
379
+ factsAreNotFindings: "These are directly observed and carry no accusation. None of them raises a risk level on its own.",
380
+ capabilitiesAreNotFindings: "What the code is able to do. A capability is not evidence that it has been or will be used.",
381
+ unknownsAreNotRisk: "Reported as unknown rather than guessed at. An unknown never contributes to a risk level.",
382
+ coverageIsNotRisk: "Coverage measures how much Decentrys knows about this subject. It is reported separately and is never added to a risk total.",
383
+ policyIsYours: "Decentrys reports evidence. What happens next is this application\u2019s decision, not ours.",
384
+ demotedExplanation: "These arrived labelled as risk signals and describe how much is known rather than what was found, so they are shown as facts and cannot raise a risk level.",
385
+ noFacts: "No facts were returned for this subject.",
386
+ noCapabilities: "No security-sensitive capabilities were identified.",
387
+ noSignals: "No threat signals were found. That is an absence of findings, not a finding that there is nothing to find.",
388
+ noUnknowns: "Every attribute checked returned an answer.",
389
+ nothingReturned: "Decentrys returned no facts, capabilities, threat signals or unknowns for this subject.",
390
+ viewDetails: "View details",
391
+ close: "Close",
392
+ detailsTitle: "Assessment details",
393
+ showEvidence: "Show evidence",
394
+ hideEvidence: "Hide evidence",
395
+ confidenceLabel: "Confidence",
396
+ confidenceCaveat: "How well the evidence supports this classification. It is not a measure of how much harm the subject could do.",
397
+ sourceLabel: "Source",
398
+ observedLabel: "Observed",
399
+ grantedByLabel: "Granted by",
400
+ hopsLabel: "Distance",
401
+ statusLabel: "Status",
402
+ analystConfirmed: "Confirmed by a Decentrys analyst against the evidence above.",
403
+ notAnalystConfirmed: "Automated finding. No analyst has confirmed it.",
404
+ cachedNotice: "Served from a local cache rather than a fresh lookup.",
405
+ modelLabel: "Model",
406
+ assessedLabel: "Assessed",
407
+ approvalHeading: "What you are about to grant",
408
+ approvalRevocation: "An allowance stays in place until it is revoked, whether or not the application is used again.",
409
+ spenderLabel: "Spender",
410
+ tokenLabel: "Token"
411
+ };
412
+ function resolveCopy(overrides) {
413
+ return overrides ? { ...DEFAULT_COPY, ...overrides } : DEFAULT_COPY;
414
+ }
415
+
416
+ // src/browser/react-jsx-runtime.ts
417
+ var Fragment2 = react_global_default.Fragment;
418
+ function create(type, props, key) {
419
+ if (key === void 0 || key === null) return react_global_default.createElement(type, props);
420
+ return react_global_default.createElement(type, { ...props ?? {}, key });
421
+ }
422
+ var jsx = create;
423
+ var jsxs = create;
424
+
425
+ // src/context.tsx
426
+ var UiContext = createContext(null);
427
+ function DecentrysUiStyles() {
428
+ return /* @__PURE__ */ jsx("style", { "data-decentrys-ui": "", children: DECENTRYS_UI_CSS });
429
+ }
430
+ function DecentrysUiProvider({
431
+ theme,
432
+ copy,
433
+ injectStyles = true,
434
+ children
435
+ }) {
436
+ return /* @__PURE__ */ jsxs(UiContext.Provider, { value: { theme, copy, stylesProvided: true }, children: [
437
+ injectStyles ? /* @__PURE__ */ jsx(DecentrysUiStyles, {}) : null,
438
+ children
439
+ ] });
440
+ }
441
+ function useUiConfig(local = {}) {
442
+ const ctx = useContext(UiContext);
443
+ const theme = ctx?.theme || local.theme ? { ...ctx?.theme ?? {}, ...local.theme ?? {} } : void 0;
444
+ return {
445
+ theme,
446
+ copy: resolveCopy({ ...ctx?.copy ?? {}, ...local.copy ?? {} }),
447
+ emitStyles: !ctx?.stylesProvided
448
+ };
449
+ }
450
+
451
+ // src/theme.ts
452
+ var THEME_TOKENS = [
453
+ "font",
454
+ "font-mono",
455
+ "radius",
456
+ "gap",
457
+ "surface",
458
+ "surface-raised",
459
+ "border",
460
+ "text",
461
+ "text-muted",
462
+ "text-faint",
463
+ "focus",
464
+ "tone-neutral",
465
+ "tone-neutral-bg",
466
+ "tone-info",
467
+ "tone-info-bg",
468
+ "tone-caution",
469
+ "tone-caution-bg",
470
+ "tone-elevated",
471
+ "tone-elevated-bg",
472
+ "tone-high",
473
+ "tone-high-bg",
474
+ "tone-critical",
475
+ "tone-critical-bg",
476
+ "tone-fact",
477
+ "tone-fact-bg",
478
+ "tone-capability",
479
+ "tone-capability-bg",
480
+ "tone-unknown",
481
+ "tone-unknown-bg",
482
+ "tone-inactive",
483
+ "tone-inactive-bg"
484
+ ];
485
+ function themeStyle(theme, base) {
486
+ if (!theme) return base;
487
+ const style = {};
488
+ for (const [token, value] of Object.entries(theme)) {
489
+ if (typeof value === "string" && value.length > 0) style[`--dcy-${token}`] = value;
490
+ }
491
+ return { ...base ?? {}, ...style };
492
+ }
493
+ function toneVar(tone) {
494
+ return `var(--dcy-tone-${tone})`;
495
+ }
496
+ function toneBgVar(tone) {
497
+ return `var(--dcy-tone-${tone}-bg)`;
498
+ }
499
+
500
+ // ../protect/dist/browser/decentrys-protect.mjs
501
+ var RISK_LEVEL_MEANING = {
502
+ NO_CRITICAL_RISK_DETECTED: "No known critical threat evidence was identified. This is not an assurance of safety.",
503
+ INFORMATIONAL: "Facts worth knowing before proceeding. Nothing here indicates danger.",
504
+ CAUTION: "A security-sensitive capability or behaviour exists that deserves attention.",
505
+ ELEVATED_RISK: "Several meaningful risk signals are present together.",
506
+ HIGH_RISK: "Strong technical or behavioural evidence of significant danger.",
507
+ CRITICAL_THREAT: "Severe threat supported by concrete evidence.",
508
+ KNOWN_MALICIOUS: "Confirmed malicious infrastructure or behaviour, verified against evidence."
509
+ };
510
+ var HISTORY_STATUS_MEANING = {
511
+ ESTABLISHED: "Substantial on-chain history is available.",
512
+ MODERATE: "Some history is available.",
513
+ LIMITED: "Little history is available yet. This is normal for anything recently deployed and is not a risk finding.",
514
+ NONE: "No history is available. This is not a risk finding."
515
+ };
516
+ var MIN_RAISING_CONFIDENCE = 0.5;
517
+
518
+ // src/presentation.ts
519
+ var RISK_LEVEL_LABEL = {
520
+ NO_CRITICAL_RISK_DETECTED: "No critical risk detected",
521
+ INFORMATIONAL: "Informational",
522
+ CAUTION: "Caution",
523
+ ELEVATED_RISK: "Elevated risk",
524
+ HIGH_RISK: "High risk",
525
+ CRITICAL_THREAT: "Critical threat",
526
+ KNOWN_MALICIOUS: "Known malicious"
527
+ };
528
+ var LEVEL_GLYPH = {
529
+ NO_CRITICAL_RISK_DETECTED: "\xB7",
530
+ INFORMATIONAL: "i",
531
+ CAUTION: "!",
532
+ ELEVATED_RISK: "!!",
533
+ HIGH_RISK: "!!!",
534
+ CRITICAL_THREAT: "!!!!",
535
+ KNOWN_MALICIOUS: "\u2715"
536
+ };
537
+ var LEVEL_TONE = {
538
+ // Neither of these two may look like a warning. Nothing in them indicates
539
+ // danger, and a new project landing on INFORMATIONAL is the common case.
540
+ NO_CRITICAL_RISK_DETECTED: "neutral",
541
+ INFORMATIONAL: "info",
542
+ CAUTION: "caution",
543
+ ELEVATED_RISK: "elevated",
544
+ HIGH_RISK: "high",
545
+ CRITICAL_THREAT: "critical",
546
+ KNOWN_MALICIOUS: "critical"
547
+ };
548
+ var LEVEL_ORDER = [
549
+ "NO_CRITICAL_RISK_DETECTED",
550
+ "INFORMATIONAL",
551
+ "CAUTION",
552
+ "ELEVATED_RISK",
553
+ "HIGH_RISK",
554
+ "CRITICAL_THREAT",
555
+ "KNOWN_MALICIOUS"
556
+ ];
557
+ function levelRank(level) {
558
+ return LEVEL_ORDER.indexOf(level);
559
+ }
560
+ function levelPresentation(level, options = {}) {
561
+ const label = options.labels?.[level] ?? RISK_LEVEL_LABEL[level];
562
+ const meaning = RISK_LEVEL_MEANING[level];
563
+ const evidenceBacked = levelRank(level) >= levelRank("ELEVATED_RISK");
564
+ return {
565
+ level,
566
+ label,
567
+ meaning,
568
+ tone: LEVEL_TONE[level],
569
+ glyph: LEVEL_GLYPH[level],
570
+ evidenceBacked,
571
+ accusatory: level === "KNOWN_MALICIOUS",
572
+ liveRegionRole: evidenceBacked ? "alert" : "status",
573
+ ariaLabel: `Decentrys assessment: ${label}. ${meaning}`
574
+ };
575
+ }
576
+ function kindPresentation(kind, copy) {
577
+ switch (kind) {
578
+ case "fact":
579
+ return { kind, label: copy.factLabel, glyph: "\xB7", tone: "fact", rule: "solid", ruleWidth: "2px" };
580
+ case "capability":
581
+ return { kind, label: copy.capabilityLabel, glyph: "\u25C7", tone: "capability", rule: "dashed", ruleWidth: "2px" };
582
+ case "signal":
583
+ return { kind, label: copy.signalLabel, glyph: "\u25B2", tone: "critical", rule: "solid", ruleWidth: "4px" };
584
+ case "unknown":
585
+ return { kind, label: copy.unknownLabel, glyph: "?", tone: "unknown", rule: "dotted", ruleWidth: "2px" };
586
+ }
587
+ }
588
+ function factPresentation(fact) {
589
+ return {
590
+ kind: "fact",
591
+ statement: fact.statement,
592
+ source: fact.source,
593
+ observedAt: formatTimestamp(fact.observedAt),
594
+ tone: "fact"
595
+ };
596
+ }
597
+ var CAPABILITY_SEVERITY_LABEL = {
598
+ INFO: "Standard",
599
+ NOTABLE: "Notable",
600
+ SIGNIFICANT: "Significant"
601
+ };
602
+ function capabilityPresentation(capability) {
603
+ return {
604
+ kind: "capability",
605
+ statement: capability.statement,
606
+ severity: capability.severity,
607
+ severityLabel: CAPABILITY_SEVERITY_LABEL[capability.severity],
608
+ grantedBy: capability.grantedBy,
609
+ tone: "capability",
610
+ emphasis: capability.severity === "SIGNIFICANT" ? "strong" : "normal"
611
+ };
612
+ }
613
+ var SEVERITY_LABEL = {
614
+ LOW: "Low",
615
+ MEDIUM: "Medium",
616
+ HIGH: "High",
617
+ CRITICAL: "Critical"
618
+ };
619
+ var SEVERITY_TONE = {
620
+ LOW: "caution",
621
+ MEDIUM: "elevated",
622
+ HIGH: "high",
623
+ CRITICAL: "critical"
624
+ };
625
+ var SIGNAL_STATUS_LABEL = {
626
+ ACTIVE: "Active",
627
+ STALE: "Stale",
628
+ RESOLVED: "Resolved",
629
+ DISPUTED_FACT: "Disputed",
630
+ REMOVED: "Withdrawn"
631
+ };
632
+ function signalPresentation(signal, copy, options = {}) {
633
+ const subject = options.subjectNoun ?? "address";
634
+ const active = signal.status === "ACTIVE";
635
+ const raising = active && signal.confidence >= MIN_RAISING_CONFIDENCE;
636
+ const statusLabel = SIGNAL_STATUS_LABEL[signal.status];
637
+ const analystConfirmed = signal.evidence.some((e) => e.analystVerified);
638
+ let inertStatement;
639
+ if (!active) {
640
+ inertStatement = `This signal is ${statusLabel.toLowerCase()} and did not affect this assessment.`;
641
+ } else if (!raising) {
642
+ inertStatement = `Reported at ${formatConfidence(signal.confidence)} confidence, which is too low to raise the risk level on its own. It is shown because it was observed, not because it counted.`;
643
+ }
644
+ const hop = hopPresentation(signal.hops, subject);
645
+ return {
646
+ kind: "signal",
647
+ explanation: signal.explanation,
648
+ severity: signal.severity,
649
+ severityLabel: SEVERITY_LABEL[signal.severity],
650
+ tone: raising ? SEVERITY_TONE[signal.severity] : "inactive",
651
+ status: signal.status,
652
+ statusLabel,
653
+ active,
654
+ raising,
655
+ inertStatement,
656
+ hops: signal.hops,
657
+ hopLabel: hop.label,
658
+ hopStatement: hop.statement,
659
+ indirect: hop.indirect,
660
+ confidence: signal.confidence,
661
+ confidenceLabel: formatConfidence(signal.confidence),
662
+ analystConfirmed,
663
+ attribution: analystConfirmed ? copy.analystConfirmed : copy.notAnalystConfirmed,
664
+ evidenceCount: signal.evidence.length,
665
+ ariaLabel: `${copy.signalLabel}, ${SEVERITY_LABEL[signal.severity].toLowerCase()} severity, ${formatConfidence(signal.confidence)} confidence, ${hop.label.toLowerCase()}. ${signal.explanation}`
666
+ };
667
+ }
668
+ function hopPresentation(hops, subjectNoun = "address") {
669
+ const n = Math.max(0, Math.trunc(hops));
670
+ if (n === 0) {
671
+ return {
672
+ hops: 0,
673
+ label: "Direct",
674
+ statement: `Observed directly on this ${subjectNoun}.`,
675
+ indirect: false
676
+ };
677
+ }
678
+ if (n === 1) {
679
+ return {
680
+ hops: 1,
681
+ label: "1 hop",
682
+ statement: `Observed one hop away. This is an inference about a counterparty of this ${subjectNoun}, not an observation about the ${subjectNoun} itself.`,
683
+ indirect: true
684
+ };
685
+ }
686
+ return {
687
+ hops: n,
688
+ label: `${n} hops`,
689
+ statement: `Observed ${n} hops away. This is an inference about a counterparty ${n} steps removed from this ${subjectNoun}, not an observation about the ${subjectNoun} itself.`,
690
+ indirect: true
691
+ };
692
+ }
693
+ function evidencePresentation(evidence, copy) {
694
+ return {
695
+ id: evidence.id,
696
+ type: evidence.type,
697
+ source: evidence.source,
698
+ observedAt: formatTimestamp(evidence.observedAt),
699
+ confidenceLabel: formatConfidence(evidence.confidence),
700
+ analystConfirmed: evidence.analystVerified,
701
+ attribution: evidence.analystVerified ? copy.analystConfirmed : copy.notAnalystConfirmed,
702
+ reference: evidence.txHash ?? evidence.contract ?? evidence.address
703
+ };
704
+ }
705
+ var UNKNOWN_REASON_LABEL = {
706
+ UNKNOWN: "Not known",
707
+ INSUFFICIENT_DATA: "Not enough data",
708
+ PROVIDER_UNAVAILABLE: "Could not be checked",
709
+ NOT_APPLICABLE: "Not applicable"
710
+ };
711
+ function unknownPresentation(unknown) {
712
+ return {
713
+ kind: "unknown",
714
+ field: unknown.field,
715
+ statement: unknown.statement,
716
+ reasonLabel: UNKNOWN_REASON_LABEL[unknown.reason],
717
+ tone: "unknown"
718
+ };
719
+ }
720
+ var HISTORY_LABEL = {
721
+ ESTABLISHED: "Established history",
722
+ MODERATE: "Some history",
723
+ LIMITED: "Limited history",
724
+ NONE: "No history"
725
+ };
726
+ function historyPresentation(status) {
727
+ return {
728
+ status,
729
+ label: HISTORY_LABEL[status],
730
+ statement: HISTORY_STATUS_MEANING[status],
731
+ tone: "neutral",
732
+ isWarning: false
733
+ };
734
+ }
735
+ function componentRows(components, copy) {
736
+ return [
737
+ { key: "technicalRisk", label: "Technical", value: components.technicalRisk, isCoverage: false },
738
+ { key: "behavioralRisk", label: "Behavioural", value: components.behavioralRisk, isCoverage: false },
739
+ {
740
+ key: "threatIntelligenceRisk",
741
+ label: "Threat intelligence",
742
+ value: components.threatIntelligenceRisk,
743
+ isCoverage: false
744
+ },
745
+ {
746
+ key: "historyConfidence",
747
+ label: "Coverage",
748
+ value: components.historyConfidence,
749
+ isCoverage: true,
750
+ caveat: copy.coverageIsNotRisk
751
+ }
752
+ ];
753
+ }
754
+ function assessmentSections(assessment, copy) {
755
+ const facts = assessment.facts ?? [];
756
+ const capabilities = assessment.capabilities ?? [];
757
+ const signals = assessment.threatSignals ?? [];
758
+ const unknowns = assessment.unknowns ?? [];
759
+ const activeSignals = signals.filter(
760
+ (s) => s.status === "ACTIVE" && s.confidence >= MIN_RAISING_CONFIDENCE
761
+ );
762
+ const inertSignals = signals.filter((s) => !activeSignals.includes(s));
763
+ const isEmpty = facts.length === 0 && capabilities.length === 0 && signals.length === 0 && unknowns.length === 0;
764
+ const parts = [];
765
+ if (facts.length) parts.push(`${facts.length} ${plural(facts.length, "fact")}`);
766
+ if (capabilities.length) {
767
+ parts.push(`${capabilities.length} ${plural(capabilities.length, "capability", "capabilities")}`);
768
+ }
769
+ if (signals.length) parts.push(`${signals.length} ${plural(signals.length, "threat signal")}`);
770
+ if (unknowns.length) parts.push(`${unknowns.length} ${plural(unknowns.length, "unknown")}`);
771
+ return {
772
+ facts,
773
+ capabilities,
774
+ activeSignals,
775
+ inertSignals,
776
+ unknowns,
777
+ isEmpty,
778
+ emptyStatement: isEmpty ? `${copy.nothingReturned} ${copy.notAnAssurance}` : void 0,
779
+ summary: parts.length ? parts.join(" \xB7 ") : copy.nothingReturned
780
+ };
781
+ }
782
+ function availabilityPresentation(assessment) {
783
+ const outage = (assessment.unknowns ?? []).filter((u) => u.reason === "PROVIDER_UNAVAILABLE");
784
+ if (outage.length === 0) return { unavailable: false, tone: "neutral" };
785
+ return {
786
+ unavailable: true,
787
+ statement: outage.map((u) => u.statement).join(" "),
788
+ tone: "neutral"
789
+ };
790
+ }
791
+ var DECISION_LABEL = {
792
+ allow: "No action requested",
793
+ inform: "For your information",
794
+ warn: "Review before continuing",
795
+ warn_strong: "Review carefully before continuing",
796
+ require_confirmation: "Explicit confirmation requested",
797
+ block: "This application will not continue"
798
+ };
799
+ var DECISION_STATEMENT = {
800
+ allow: "This application\u2019s policy asks for nothing further.",
801
+ inform: "Shown because this application\u2019s policy asks that you see it.",
802
+ warn: "This application\u2019s policy asks you to read the detail before continuing.",
803
+ warn_strong: "This application\u2019s policy asks you to read the detail carefully before continuing.",
804
+ require_confirmation: "This application\u2019s policy asks you to confirm explicitly before continuing.",
805
+ block: "This application\u2019s policy stops here. That decision belongs to this application; Decentrys reports evidence and does not stop anything."
806
+ };
807
+ function decisionPresentation(decision) {
808
+ return {
809
+ action: decision.action,
810
+ label: DECISION_LABEL[decision.action],
811
+ statement: DECISION_STATEMENT[decision.action],
812
+ reason: decision.reason,
813
+ blocksUi: false
814
+ };
815
+ }
816
+ function approvalPresentation(details) {
817
+ const spenderName = details.spenderLabel?.trim() || shortenAddress(details.spender);
818
+ const assetName = details.symbol?.trim() || `tokens at ${shortenAddress(details.token)}`;
819
+ if (details.amount === void 0 || details.amount === null || details.amount === "") {
820
+ return {
821
+ unlimited: false,
822
+ amountKnown: false,
823
+ amountLabel: "Not decoded",
824
+ scopeLabel: "Allowance amount not decoded",
825
+ statement: `The allowance amount could not be decoded, so how much this grants ${spenderName} over your ${assetName} is not known.`,
826
+ spenderName,
827
+ assetName,
828
+ tone: "capability",
829
+ emphasis: "strong"
830
+ };
831
+ }
832
+ if (isUnlimitedAllowance(details.amount)) {
833
+ return {
834
+ unlimited: true,
835
+ amountKnown: true,
836
+ amountLabel: "Unlimited",
837
+ scopeLabel: "Unlimited allowance",
838
+ statement: `This grants ${spenderName} permission to spend an unlimited amount of your ${assetName}, now and at any time in the future, until it is revoked.`,
839
+ spenderName,
840
+ assetName,
841
+ tone: "capability",
842
+ emphasis: "strong"
843
+ };
844
+ }
845
+ const baseUnits = details.decimals === void 0;
846
+ const figure = formatTokenAmount(details.amount, details.decimals);
847
+ const amountLabel = baseUnits ? `${figure} base units` : `${figure} ${assetName}`;
848
+ return {
849
+ unlimited: false,
850
+ amountKnown: true,
851
+ amountLabel,
852
+ scopeLabel: "Limited allowance",
853
+ statement: `This grants ${spenderName} permission to spend up to ${figure} ${baseUnits ? `base units of ${assetName}` : assetName}, until it is revoked.`,
854
+ spenderName,
855
+ assetName,
856
+ tone: "capability",
857
+ emphasis: "normal"
858
+ };
859
+ }
860
+ function isUnlimitedAllowance(amount) {
861
+ if (amount.trim().toLowerCase() === "unlimited") return true;
862
+ try {
863
+ return BigInt(amount) >= 1n << 255n;
864
+ } catch {
865
+ return false;
866
+ }
867
+ }
868
+ function formatConfidence(confidence) {
869
+ const clamped = Math.min(1, Math.max(0, confidence));
870
+ return `${Math.round(clamped * 100)}%`;
871
+ }
872
+ function formatTimestamp(iso) {
873
+ const ms = Date.parse(iso);
874
+ if (Number.isNaN(ms)) return iso;
875
+ return `${new Date(ms).toISOString().slice(0, 16).replace("T", " ")} UTC`;
876
+ }
877
+ function shortenAddress(address, lead = 6, tail = 4) {
878
+ if (address.length <= lead + tail + 1) return address;
879
+ return `${address.slice(0, lead)}\u2026${address.slice(-tail)}`;
880
+ }
881
+ function formatTokenAmount(amount, decimals) {
882
+ let value;
883
+ try {
884
+ value = BigInt(amount);
885
+ } catch {
886
+ return amount;
887
+ }
888
+ if (decimals === void 0 || decimals < 0) return group(value.toString());
889
+ const negative = value < 0n;
890
+ const abs = negative ? -value : value;
891
+ const scale = 10n ** BigInt(decimals);
892
+ const whole = abs / scale;
893
+ const fraction = (abs % scale).toString().padStart(decimals, "0").replace(/0+$/, "");
894
+ const text = fraction ? `${group(whole.toString())}.${fraction}` : group(whole.toString());
895
+ return negative ? `-${text}` : text;
896
+ }
897
+ function group(digits) {
898
+ return digits.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
899
+ }
900
+ function plural(count, singular, pluralForm) {
901
+ if (count === 1) return singular;
902
+ return pluralForm ?? `${singular}s`;
903
+ }
904
+
905
+ // src/primitives.tsx
906
+ function cx(...parts) {
907
+ return parts.filter(Boolean).join(" ");
908
+ }
909
+ function StyleGate({ emit }) {
910
+ return emit ? /* @__PURE__ */ jsx(DecentrysUiStyles, {}) : null;
911
+ }
912
+ function LevelBadge({ level }) {
913
+ return /* @__PURE__ */ jsxs("span", { className: "dcy-badge", "data-dcy-tone": level.tone, "data-dcy-level": level.level, children: [
914
+ /* @__PURE__ */ jsx("span", { className: "dcy-glyph", "aria-hidden": "true", children: level.glyph }),
915
+ /* @__PURE__ */ jsx("span", { children: level.label })
916
+ ] });
917
+ }
918
+ function Chip({ children, title }) {
919
+ return /* @__PURE__ */ jsx("span", { className: "dcy-chip", title, children });
920
+ }
921
+ function Mono({ children }) {
922
+ return /* @__PURE__ */ jsx("span", { className: "dcy-mono", children });
923
+ }
924
+ function ScreenReaderText({ children }) {
925
+ return /* @__PURE__ */ jsx("span", { className: "dcy-sr", children });
926
+ }
927
+ function Section({
928
+ title,
929
+ note,
930
+ children
931
+ }) {
932
+ return /* @__PURE__ */ jsxs("section", { className: "dcy-section", children: [
933
+ /* @__PURE__ */ jsx("h4", { className: "dcy-section-title", children: title }),
934
+ note ? /* @__PURE__ */ jsx("p", { className: "dcy-note", children: note }) : null,
935
+ children
936
+ ] });
937
+ }
938
+ function EmptyNote({ children }) {
939
+ return /* @__PURE__ */ jsx("p", { className: "dcy-note", "data-dcy-empty": "", children });
940
+ }
941
+ function FactRow({ fact, copy }) {
942
+ const kind = kindPresentation("fact", copy);
943
+ const p = factPresentation(fact);
944
+ return /* @__PURE__ */ jsxs(
945
+ "li",
946
+ {
947
+ className: "dcy-row",
948
+ "data-dcy-kind": "fact",
949
+ "data-dcy-tone": p.tone,
950
+ "data-dcy-rule": kind.rule,
951
+ children: [
952
+ /* @__PURE__ */ jsx("span", { className: "dcy-glyph", "aria-hidden": "true", children: kind.glyph }),
953
+ /* @__PURE__ */ jsxs("div", { className: "dcy-row-body", children: [
954
+ /* @__PURE__ */ jsxs("p", { className: "dcy-row-statement", children: [
955
+ /* @__PURE__ */ jsx(Chip, { children: kind.label }),
956
+ " ",
957
+ p.statement
958
+ ] }),
959
+ /* @__PURE__ */ jsxs("p", { className: "dcy-row-meta", children: [
960
+ /* @__PURE__ */ jsxs("span", { children: [
961
+ copy.sourceLabel,
962
+ ": ",
963
+ p.source
964
+ ] }),
965
+ /* @__PURE__ */ jsxs("span", { children: [
966
+ copy.observedLabel,
967
+ ": ",
968
+ p.observedAt
969
+ ] })
970
+ ] })
971
+ ] })
972
+ ]
973
+ }
974
+ );
975
+ }
976
+ function CapabilityRow({
977
+ capability,
978
+ copy
979
+ }) {
980
+ const kind = kindPresentation("capability", copy);
981
+ const p = capabilityPresentation(capability);
982
+ return /* @__PURE__ */ jsxs(
983
+ "li",
984
+ {
985
+ className: "dcy-row",
986
+ "data-dcy-kind": "capability",
987
+ "data-dcy-tone": p.tone,
988
+ "data-dcy-rule": kind.rule,
989
+ "data-dcy-severity": p.severity,
990
+ children: [
991
+ /* @__PURE__ */ jsx("span", { className: "dcy-glyph", "aria-hidden": "true", children: kind.glyph }),
992
+ /* @__PURE__ */ jsxs("div", { className: "dcy-row-body", children: [
993
+ /* @__PURE__ */ jsxs("p", { className: cx("dcy-row-statement", p.emphasis === "strong" && "dcy-row-strong"), children: [
994
+ /* @__PURE__ */ jsx(Chip, { children: kind.label }),
995
+ " ",
996
+ /* @__PURE__ */ jsx(Chip, { children: p.severityLabel }),
997
+ " ",
998
+ p.statement
999
+ ] }),
1000
+ p.grantedBy ? /* @__PURE__ */ jsx("p", { className: "dcy-row-meta", children: /* @__PURE__ */ jsxs("span", { children: [
1001
+ copy.grantedByLabel,
1002
+ ": ",
1003
+ /* @__PURE__ */ jsx(Mono, { children: p.grantedBy })
1004
+ ] }) }) : null
1005
+ ] })
1006
+ ]
1007
+ }
1008
+ );
1009
+ }
1010
+ function UnknownRow({
1011
+ unknown,
1012
+ copy
1013
+ }) {
1014
+ const kind = kindPresentation("unknown", copy);
1015
+ const p = unknownPresentation(unknown);
1016
+ return /* @__PURE__ */ jsxs(
1017
+ "li",
1018
+ {
1019
+ className: "dcy-row",
1020
+ "data-dcy-kind": "unknown",
1021
+ "data-dcy-tone": p.tone,
1022
+ "data-dcy-rule": kind.rule,
1023
+ children: [
1024
+ /* @__PURE__ */ jsx("span", { className: "dcy-glyph", "aria-hidden": "true", children: kind.glyph }),
1025
+ /* @__PURE__ */ jsxs("div", { className: "dcy-row-body", children: [
1026
+ /* @__PURE__ */ jsxs("p", { className: "dcy-row-statement", children: [
1027
+ /* @__PURE__ */ jsx(Chip, { children: kind.label }),
1028
+ " ",
1029
+ p.statement
1030
+ ] }),
1031
+ /* @__PURE__ */ jsxs("p", { className: "dcy-row-meta", children: [
1032
+ /* @__PURE__ */ jsx("span", { children: p.field }),
1033
+ /* @__PURE__ */ jsx("span", { children: p.reasonLabel })
1034
+ ] })
1035
+ ] })
1036
+ ]
1037
+ }
1038
+ );
1039
+ }
1040
+ function EvidenceList({
1041
+ evidence,
1042
+ copy
1043
+ }) {
1044
+ if (evidence.length === 0) return null;
1045
+ return /* @__PURE__ */ jsxs("div", { className: "dcy-section", children: [
1046
+ /* @__PURE__ */ jsx("h5", { className: "dcy-section-title", children: copy.evidenceHeading }),
1047
+ evidence.map((item) => {
1048
+ const e = evidencePresentation(item, copy);
1049
+ return /* @__PURE__ */ jsxs("dl", { className: "dcy-kv", children: [
1050
+ /* @__PURE__ */ jsx("dt", { children: copy.sourceLabel }),
1051
+ /* @__PURE__ */ jsx("dd", { children: e.source }),
1052
+ /* @__PURE__ */ jsx("dt", { children: "Type" }),
1053
+ /* @__PURE__ */ jsx("dd", { children: e.type }),
1054
+ /* @__PURE__ */ jsx("dt", { children: copy.observedLabel }),
1055
+ /* @__PURE__ */ jsx("dd", { children: e.observedAt }),
1056
+ /* @__PURE__ */ jsx("dt", { children: copy.confidenceLabel }),
1057
+ /* @__PURE__ */ jsx("dd", { children: e.confidenceLabel }),
1058
+ e.reference ? /* @__PURE__ */ jsxs(Fragment2, { children: [
1059
+ /* @__PURE__ */ jsx("dt", { children: "Reference" }),
1060
+ /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx(Mono, { children: e.reference }) })
1061
+ ] }) : null,
1062
+ /* @__PURE__ */ jsx("dt", { children: "Attribution" }),
1063
+ /* @__PURE__ */ jsx("dd", { children: e.attribution }),
1064
+ /* @__PURE__ */ jsx("dt", { children: "Evidence id" }),
1065
+ /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx(Mono, { children: e.id }) })
1066
+ ] }, e.id);
1067
+ })
1068
+ ] });
1069
+ }
1070
+ function SignalCard({
1071
+ signal,
1072
+ copy,
1073
+ subjectNoun,
1074
+ showEvidence = false
1075
+ }) {
1076
+ const kind = kindPresentation("signal", copy);
1077
+ const p = signalPresentation(signal, copy, { subjectNoun });
1078
+ return /* @__PURE__ */ jsxs(
1079
+ "li",
1080
+ {
1081
+ className: "dcy-signal",
1082
+ "data-dcy-kind": "signal",
1083
+ "data-dcy-tone": p.tone,
1084
+ "data-dcy-severity": p.severity,
1085
+ "data-dcy-raising": String(p.raising),
1086
+ "data-dcy-hops": String(p.hops),
1087
+ children: [
1088
+ /* @__PURE__ */ jsxs("div", { className: "dcy-signal-head", children: [
1089
+ /* @__PURE__ */ jsx("span", { className: "dcy-glyph", "aria-hidden": "true", children: kind.glyph }),
1090
+ /* @__PURE__ */ jsx(Chip, { children: kind.label }),
1091
+ /* @__PURE__ */ jsxs(Chip, { children: [
1092
+ p.severityLabel,
1093
+ " severity"
1094
+ ] }),
1095
+ /* @__PURE__ */ jsxs(Chip, { title: copy.confidenceCaveat, children: [
1096
+ p.confidenceLabel,
1097
+ " ",
1098
+ copy.confidenceLabel.toLowerCase()
1099
+ ] }),
1100
+ /* @__PURE__ */ jsx(Chip, { children: p.hopLabel }),
1101
+ p.active ? null : /* @__PURE__ */ jsx(Chip, { children: p.statusLabel })
1102
+ ] }),
1103
+ /* @__PURE__ */ jsx("p", { className: "dcy-row-statement dcy-row-strong", children: p.explanation }),
1104
+ /* @__PURE__ */ jsx("p", { className: "dcy-note", children: p.hopStatement }),
1105
+ p.inertStatement ? /* @__PURE__ */ jsx("p", { className: "dcy-note", children: p.inertStatement }) : null,
1106
+ /* @__PURE__ */ jsx("p", { className: "dcy-note", children: p.attribution }),
1107
+ showEvidence ? /* @__PURE__ */ jsx(EvidenceList, { evidence: signal.evidence, copy }) : null
1108
+ ]
1109
+ }
1110
+ );
1111
+ }
1112
+ function SignalList({
1113
+ signals,
1114
+ copy,
1115
+ subjectNoun,
1116
+ showEvidence
1117
+ }) {
1118
+ if (signals.length === 0) return /* @__PURE__ */ jsx(EmptyNote, { children: copy.noSignals });
1119
+ return /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: signals.map((signal, index) => /* @__PURE__ */ jsx(
1120
+ SignalCard,
1121
+ {
1122
+ signal,
1123
+ copy,
1124
+ subjectNoun,
1125
+ showEvidence
1126
+ },
1127
+ `${signal.type}:${signal.createdAt}:${index}`
1128
+ )) });
1129
+ }
1130
+ function HistoryNote({
1131
+ status,
1132
+ copy
1133
+ }) {
1134
+ const h = historyPresentation(status);
1135
+ return /* @__PURE__ */ jsxs("p", { className: "dcy-note", "data-dcy-tone": h.tone, "data-dcy-history": h.status, children: [
1136
+ /* @__PURE__ */ jsx(Chip, { children: h.label }),
1137
+ " ",
1138
+ h.statement
1139
+ ] });
1140
+ }
1141
+ function AvailabilityNote({ assessment }) {
1142
+ const a = availabilityPresentation(assessment);
1143
+ if (!a.unavailable) return null;
1144
+ return /* @__PURE__ */ jsx("p", { className: "dcy-note", "data-dcy-tone": a.tone, "data-dcy-unavailable": "", children: a.statement });
1145
+ }
1146
+ function DemotedNote({
1147
+ demotedSignals,
1148
+ copy
1149
+ }) {
1150
+ if (!demotedSignals || demotedSignals.length === 0) return null;
1151
+ return /* @__PURE__ */ jsx(Section, { title: copy.demotedHeading, note: copy.demotedExplanation, children: /* @__PURE__ */ jsx("p", { className: "dcy-row-meta", children: demotedSignals.map((type) => /* @__PURE__ */ jsx(Chip, { children: type }, type)) }) });
1152
+ }
1153
+ function ExplanationList({ explanation }) {
1154
+ if (explanation.length === 0) return null;
1155
+ return /* @__PURE__ */ jsx("ul", { className: "dcy-list", "data-dcy-explanation": "", children: explanation.map((line, index) => /* @__PURE__ */ jsx("li", { className: "dcy-note", children: line }, `${index}:${line.slice(0, 24)}`)) });
1156
+ }
1157
+ function LevelHeader({
1158
+ assessment,
1159
+ copy,
1160
+ levelLabels,
1161
+ children
1162
+ }) {
1163
+ const level = levelPresentation(assessment.riskLevel, { labels: levelLabels });
1164
+ return /* @__PURE__ */ jsxs("div", { className: "dcy-head", children: [
1165
+ /* @__PURE__ */ jsxs("div", { className: "dcy-head-main", children: [
1166
+ /* @__PURE__ */ jsx("h3", { className: "dcy-title", children: level.label }),
1167
+ /* @__PURE__ */ jsx("p", { className: "dcy-meaning", children: level.meaning })
1168
+ ] }),
1169
+ /* @__PURE__ */ jsx(LevelBadge, { level }),
1170
+ children
1171
+ ] });
1172
+ }
1173
+
1174
+ // src/components/TransactionRiskBanner.tsx
1175
+ function TransactionRiskBanner({
1176
+ result,
1177
+ subjectNoun = "transaction",
1178
+ actions,
1179
+ onViewDetails,
1180
+ maxExplanationLines = 4,
1181
+ className,
1182
+ style,
1183
+ theme,
1184
+ copy: copyOverrides,
1185
+ levelLabels
1186
+ }) {
1187
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1188
+ const { assessment, decision, cached, demotedSignals } = result;
1189
+ const level = levelPresentation(assessment.riskLevel, { labels: levelLabels });
1190
+ const sections = assessmentSections(assessment, copy);
1191
+ const policy = decisionPresentation(decision);
1192
+ return /* @__PURE__ */ jsxs(
1193
+ "section",
1194
+ {
1195
+ className: cx("dcy", "dcy-card", className),
1196
+ style: themeStyle(mergedTheme, style),
1197
+ "data-dcy-component": "transaction-risk-banner",
1198
+ "data-dcy-level": assessment.riskLevel,
1199
+ "data-dcy-tone": level.tone,
1200
+ role: level.liveRegionRole,
1201
+ "aria-label": level.ariaLabel,
1202
+ children: [
1203
+ /* @__PURE__ */ jsx(StyleGate, { emit: emitStyles }),
1204
+ /* @__PURE__ */ jsx(LevelHeader, { assessment, copy, levelLabels }),
1205
+ /* @__PURE__ */ jsx(AvailabilityNote, { assessment }),
1206
+ /* @__PURE__ */ jsx(ExplanationList, { explanation: assessment.explanation.slice(0, maxExplanationLines) }),
1207
+ sections.isEmpty ? /* @__PURE__ */ jsx(EmptyNote, { children: sections.emptyStatement }) : /* @__PURE__ */ jsx("p", { className: "dcy-note", "data-dcy-summary": "", children: sections.summary }),
1208
+ sections.activeSignals.length > 0 ? /* @__PURE__ */ jsx(Section, { title: copy.signalsHeading, children: /* @__PURE__ */ jsx(SignalList, { signals: sections.activeSignals, copy, subjectNoun }) }) : null,
1209
+ sections.inertSignals.length > 0 ? /* @__PURE__ */ jsxs(EmptyNote, { children: [
1210
+ sections.inertSignals.length,
1211
+ " ",
1212
+ plural(sections.inertSignals.length, "further signal was", "further signals were"),
1213
+ " ",
1214
+ "observed and did not affect this assessment. They are listed in full in the details."
1215
+ ] }) : null,
1216
+ sections.unknowns.length > 0 ? /* @__PURE__ */ jsx(Section, { title: copy.unknownsHeading, note: copy.unknownsAreNotRisk, children: /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: sections.unknowns.map((unknown) => /* @__PURE__ */ jsx(UnknownRow, { unknown, copy }, unknown.field)) }) }) : null,
1217
+ /* @__PURE__ */ jsx(HistoryNote, { status: assessment.historyStatus, copy }),
1218
+ /* @__PURE__ */ jsx(DemotedNote, { demotedSignals, copy }),
1219
+ /* @__PURE__ */ jsxs("p", { className: "dcy-note", "data-dcy-decision": policy.action, children: [
1220
+ /* @__PURE__ */ jsx("span", { className: "dcy-chip", children: policy.label }),
1221
+ " ",
1222
+ policy.statement
1223
+ ] }),
1224
+ cached ? /* @__PURE__ */ jsx("p", { className: "dcy-note", children: copy.cachedNotice }) : null,
1225
+ onViewDetails || actions ? /* @__PURE__ */ jsxs("div", { className: "dcy-head", children: [
1226
+ onViewDetails ? /* @__PURE__ */ jsx("button", { type: "button", className: "dcy-btn dcy-btn-quiet", onClick: onViewDetails, children: copy.viewDetails }) : null,
1227
+ actions
1228
+ ] }) : null
1229
+ ]
1230
+ }
1231
+ );
1232
+ }
1233
+
1234
+ // src/components/ApprovalWarning.tsx
1235
+ function ApprovalWarning({
1236
+ result,
1237
+ approval,
1238
+ actions,
1239
+ onViewDetails,
1240
+ className,
1241
+ style,
1242
+ theme,
1243
+ copy: copyOverrides,
1244
+ levelLabels
1245
+ }) {
1246
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1247
+ const { assessment, decision, demotedSignals } = result;
1248
+ const level = levelPresentation(assessment.riskLevel, { labels: levelLabels });
1249
+ const scope = approvalPresentation(approval);
1250
+ const sections = assessmentSections(assessment, copy);
1251
+ const policy = decisionPresentation(decision);
1252
+ return /* @__PURE__ */ jsxs(
1253
+ "section",
1254
+ {
1255
+ className: cx("dcy", "dcy-card", className),
1256
+ style: themeStyle(mergedTheme, style),
1257
+ "data-dcy-component": "approval-warning",
1258
+ "data-dcy-level": assessment.riskLevel,
1259
+ "data-dcy-tone": level.tone,
1260
+ "data-dcy-unlimited": String(scope.unlimited),
1261
+ role: level.liveRegionRole,
1262
+ "aria-label": `${scope.scopeLabel}. ${level.ariaLabel}`,
1263
+ children: [
1264
+ /* @__PURE__ */ jsx(StyleGate, { emit: emitStyles }),
1265
+ /* @__PURE__ */ jsx(LevelHeader, { assessment, copy, levelLabels }),
1266
+ /* @__PURE__ */ jsx(AvailabilityNote, { assessment }),
1267
+ /* @__PURE__ */ jsxs(Section, { title: copy.approvalHeading, children: [
1268
+ /* @__PURE__ */ jsxs(
1269
+ "div",
1270
+ {
1271
+ className: "dcy-row",
1272
+ "data-dcy-kind": "capability",
1273
+ "data-dcy-tone": scope.tone,
1274
+ "data-dcy-rule": "dashed",
1275
+ children: [
1276
+ /* @__PURE__ */ jsx("span", { className: "dcy-glyph", "aria-hidden": "true", children: "\u25C7" }),
1277
+ /* @__PURE__ */ jsxs("div", { className: "dcy-row-body", children: [
1278
+ /* @__PURE__ */ jsxs("p", { className: cx("dcy-row-statement", scope.emphasis === "strong" && "dcy-row-strong"), children: [
1279
+ /* @__PURE__ */ jsx(Chip, { children: scope.scopeLabel }),
1280
+ " ",
1281
+ scope.statement
1282
+ ] }),
1283
+ /* @__PURE__ */ jsx("p", { className: "dcy-note", children: copy.approvalRevocation })
1284
+ ] })
1285
+ ]
1286
+ }
1287
+ ),
1288
+ /* @__PURE__ */ jsxs("dl", { className: "dcy-kv", children: [
1289
+ /* @__PURE__ */ jsx("dt", { children: copy.spenderLabel }),
1290
+ /* @__PURE__ */ jsxs("dd", { children: [
1291
+ /* @__PURE__ */ jsx(Mono, { children: approval.spender }),
1292
+ approval.spenderLabel ? ` \u2014 ${approval.spenderLabel}` : null
1293
+ ] }),
1294
+ /* @__PURE__ */ jsx("dt", { children: copy.tokenLabel }),
1295
+ /* @__PURE__ */ jsxs("dd", { children: [
1296
+ /* @__PURE__ */ jsx(Mono, { children: approval.token }),
1297
+ approval.symbol ? ` \u2014 ${approval.symbol}` : null
1298
+ ] }),
1299
+ /* @__PURE__ */ jsx("dt", { children: "Amount" }),
1300
+ /* @__PURE__ */ jsx("dd", { children: scope.amountLabel })
1301
+ ] })
1302
+ ] }),
1303
+ /* @__PURE__ */ jsx(Section, { title: copy.signalsHeading, children: /* @__PURE__ */ jsx(
1304
+ SignalList,
1305
+ {
1306
+ signals: [...sections.activeSignals, ...sections.inertSignals],
1307
+ copy,
1308
+ subjectNoun: "spender"
1309
+ }
1310
+ ) }),
1311
+ sections.unknowns.length > 0 ? /* @__PURE__ */ jsx(Section, { title: copy.unknownsHeading, note: copy.unknownsAreNotRisk, children: /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: sections.unknowns.map((unknown) => /* @__PURE__ */ jsx(UnknownRow, { unknown, copy }, unknown.field)) }) }) : null,
1312
+ /* @__PURE__ */ jsx(HistoryNote, { status: assessment.historyStatus, copy }),
1313
+ demotedSignals && demotedSignals.length > 0 ? /* @__PURE__ */ jsxs(EmptyNote, { children: [
1314
+ copy.demotedHeading,
1315
+ ": ",
1316
+ demotedSignals.join(", "),
1317
+ ". ",
1318
+ copy.demotedExplanation
1319
+ ] }) : null,
1320
+ /* @__PURE__ */ jsxs("p", { className: "dcy-note", "data-dcy-decision": policy.action, children: [
1321
+ /* @__PURE__ */ jsx("span", { className: "dcy-chip", children: policy.label }),
1322
+ " ",
1323
+ policy.statement
1324
+ ] }),
1325
+ onViewDetails || actions ? /* @__PURE__ */ jsxs("div", { className: "dcy-head", children: [
1326
+ onViewDetails ? /* @__PURE__ */ jsx("button", { type: "button", className: "dcy-btn dcy-btn-quiet", onClick: onViewDetails, children: copy.viewDetails }) : null,
1327
+ actions
1328
+ ] }) : null
1329
+ ]
1330
+ }
1331
+ );
1332
+ }
1333
+
1334
+ // src/components/ContractFacts.tsx
1335
+ function ContractFacts({
1336
+ assessment,
1337
+ demotedSignals,
1338
+ onViewSignals,
1339
+ title,
1340
+ className,
1341
+ style,
1342
+ theme,
1343
+ copy: copyOverrides
1344
+ }) {
1345
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1346
+ const sections = assessmentSections(assessment, copy);
1347
+ const signalCount = sections.activeSignals.length + sections.inertSignals.length;
1348
+ return /* @__PURE__ */ jsxs(
1349
+ "section",
1350
+ {
1351
+ className: cx("dcy", "dcy-card", className),
1352
+ style: themeStyle(mergedTheme, style),
1353
+ "data-dcy-component": "contract-facts",
1354
+ "aria-label": title ?? "Contract facts and capabilities",
1355
+ children: [
1356
+ /* @__PURE__ */ jsx(StyleGate, { emit: emitStyles }),
1357
+ title ? /* @__PURE__ */ jsx("h3", { className: "dcy-title", children: title }) : null,
1358
+ /* @__PURE__ */ jsx(AvailabilityNote, { assessment }),
1359
+ /* @__PURE__ */ jsx(Section, { title: copy.capabilitiesHeading, note: copy.capabilitiesAreNotFindings, children: sections.capabilities.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: copy.noCapabilities }) : /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: sections.capabilities.map((capability, index) => /* @__PURE__ */ jsx(
1360
+ CapabilityRow,
1361
+ {
1362
+ capability,
1363
+ copy
1364
+ },
1365
+ `${capability.type}:${index}`
1366
+ )) }) }),
1367
+ /* @__PURE__ */ jsx(Section, { title: copy.factsHeading, note: copy.factsAreNotFindings, children: sections.facts.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: copy.noFacts }) : /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: sections.facts.map((fact, index) => /* @__PURE__ */ jsx(FactRow, { fact, copy }, `${fact.type}:${index}`)) }) }),
1368
+ /* @__PURE__ */ jsx(Section, { title: copy.unknownsHeading, note: copy.unknownsAreNotRisk, children: sections.unknowns.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: copy.noUnknowns }) : /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: sections.unknowns.map((unknown) => /* @__PURE__ */ jsx(UnknownRow, { unknown, copy }, unknown.field)) }) }),
1369
+ /* @__PURE__ */ jsx(HistoryNote, { status: assessment.historyStatus, copy }),
1370
+ /* @__PURE__ */ jsx(DemotedNote, { demotedSignals, copy }),
1371
+ signalCount > 0 ? /* @__PURE__ */ jsxs("p", { className: "dcy-note", "data-dcy-signals-elsewhere": "", children: [
1372
+ signalCount,
1373
+ " ",
1374
+ plural(signalCount, "threat signal is", "threat signals are"),
1375
+ " reported for this subject and ",
1376
+ plural(signalCount, "is", "are"),
1377
+ " shown separately.",
1378
+ onViewSignals ? /* @__PURE__ */ jsxs(Fragment2, { children: [
1379
+ " ",
1380
+ /* @__PURE__ */ jsx("button", { type: "button", className: "dcy-btn dcy-btn-quiet", onClick: onViewSignals, children: copy.signalsHeading })
1381
+ ] }) : null
1382
+ ] }) : /* @__PURE__ */ jsx("p", { className: "dcy-note", children: copy.noSignals })
1383
+ ]
1384
+ }
1385
+ );
1386
+ }
1387
+
1388
+ // src/components/ThreatSignalList.tsx
1389
+ function ThreatSignalList({
1390
+ signals,
1391
+ subjectNoun = "address",
1392
+ defaultShowEvidence = false,
1393
+ showEvidenceToggle = true,
1394
+ title,
1395
+ className,
1396
+ style,
1397
+ theme,
1398
+ copy: copyOverrides
1399
+ }) {
1400
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1401
+ const [showEvidence, setShowEvidence] = useState(defaultShowEvidence);
1402
+ const evidenceVisible = showEvidenceToggle ? showEvidence : defaultShowEvidence;
1403
+ return /* @__PURE__ */ jsxs(
1404
+ "section",
1405
+ {
1406
+ className: cx("dcy", "dcy-card", className),
1407
+ style: themeStyle(mergedTheme, style),
1408
+ "data-dcy-component": "threat-signal-list",
1409
+ "data-dcy-signal-count": String(signals.length),
1410
+ "aria-label": title ?? copy.signalsHeading,
1411
+ children: [
1412
+ /* @__PURE__ */ jsx(StyleGate, { emit: emitStyles }),
1413
+ /* @__PURE__ */ jsx(Section, { title: title ?? copy.signalsHeading, children: signals.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: copy.noSignals }) : /* @__PURE__ */ jsx(
1414
+ SignalList,
1415
+ {
1416
+ signals,
1417
+ copy,
1418
+ subjectNoun,
1419
+ showEvidence: evidenceVisible
1420
+ }
1421
+ ) }),
1422
+ signals.length > 0 && showEvidenceToggle ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
1423
+ "button",
1424
+ {
1425
+ type: "button",
1426
+ className: "dcy-btn dcy-btn-quiet",
1427
+ "aria-expanded": evidenceVisible,
1428
+ onClick: () => setShowEvidence((value) => !value),
1429
+ children: evidenceVisible ? copy.hideEvidence : copy.showEvidence
1430
+ }
1431
+ ) }) : null
1432
+ ]
1433
+ }
1434
+ );
1435
+ }
1436
+
1437
+ // src/browser/react-dom-global.ts
1438
+ function resolveReactDom() {
1439
+ const candidate = globalThis.ReactDOM;
1440
+ if (!candidate || typeof candidate.createPortal !== "function") {
1441
+ throw new Error(
1442
+ "DecentrysUI: RiskDetailsModal needs ReactDOM on the page (window.ReactDOM) to portal into document.body. Load ReactDOM, or pass portal={false} to render the dialog in place."
1443
+ );
1444
+ }
1445
+ return candidate;
1446
+ }
1447
+ var createPortal = (...args) => resolveReactDom().createPortal(...args);
1448
+
1449
+ // src/components/RiskDetailsModal.tsx
1450
+ function RiskDetailsModal({
1451
+ result,
1452
+ open,
1453
+ onClose,
1454
+ subjectNoun = "address",
1455
+ portal = true,
1456
+ className,
1457
+ style,
1458
+ theme,
1459
+ copy: copyOverrides,
1460
+ levelLabels
1461
+ }) {
1462
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1463
+ const titleId = useId();
1464
+ const dialogRef = useRef(null);
1465
+ const restoreFocusTo = useRef(null);
1466
+ useEffect(() => {
1467
+ if (!open) return void 0;
1468
+ restoreFocusTo.current = typeof document === "undefined" ? null : document.activeElement;
1469
+ dialogRef.current?.focus();
1470
+ const onKeyDown = (event) => {
1471
+ if (event.key === "Escape") {
1472
+ event.stopPropagation();
1473
+ onClose();
1474
+ }
1475
+ };
1476
+ document.addEventListener("keydown", onKeyDown);
1477
+ return () => {
1478
+ document.removeEventListener("keydown", onKeyDown);
1479
+ const previous = restoreFocusTo.current;
1480
+ if (previous instanceof HTMLElement) previous.focus();
1481
+ };
1482
+ }, [open, onClose]);
1483
+ if (!open) return null;
1484
+ const { assessment, subject, decision, cached, demotedSignals } = result;
1485
+ const level = levelPresentation(assessment.riskLevel, { labels: levelLabels });
1486
+ const sections = assessmentSections(assessment, copy);
1487
+ const policy = decisionPresentation(decision);
1488
+ const dialog = /* @__PURE__ */ jsxs(
1489
+ "div",
1490
+ {
1491
+ className: cx("dcy", "dcy-backdrop"),
1492
+ style: themeStyle(mergedTheme),
1493
+ "data-dcy-component": "risk-details-modal",
1494
+ onClick: (event) => {
1495
+ if (event.target === event.currentTarget) onClose();
1496
+ },
1497
+ children: [
1498
+ /* @__PURE__ */ jsx(StyleGate, { emit: emitStyles }),
1499
+ /* @__PURE__ */ jsxs(
1500
+ "div",
1501
+ {
1502
+ ref: dialogRef,
1503
+ role: "dialog",
1504
+ "aria-modal": "true",
1505
+ "aria-labelledby": titleId,
1506
+ tabIndex: -1,
1507
+ className: cx("dcy-modal", className),
1508
+ style,
1509
+ "data-dcy-level": assessment.riskLevel,
1510
+ "data-dcy-tone": level.tone,
1511
+ children: [
1512
+ /* @__PURE__ */ jsxs("div", { className: "dcy-modal-head", children: [
1513
+ /* @__PURE__ */ jsx("h2", { className: "dcy-title", id: titleId, children: copy.detailsTitle }),
1514
+ /* @__PURE__ */ jsx("button", { type: "button", className: "dcy-btn", onClick: onClose, children: copy.close })
1515
+ ] }),
1516
+ /* @__PURE__ */ jsxs("div", { className: "dcy-head", children: [
1517
+ /* @__PURE__ */ jsxs("div", { className: "dcy-head-main", children: [
1518
+ /* @__PURE__ */ jsx("h3", { className: "dcy-title", children: level.label }),
1519
+ /* @__PURE__ */ jsx("p", { className: "dcy-meaning", children: level.meaning })
1520
+ ] }),
1521
+ /* @__PURE__ */ jsx(LevelBadge, { level })
1522
+ ] }),
1523
+ /* @__PURE__ */ jsxs("dl", { className: "dcy-kv", children: [
1524
+ /* @__PURE__ */ jsx("dt", { children: "Subject" }),
1525
+ /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx(Mono, { children: subject.identifier }) }),
1526
+ /* @__PURE__ */ jsx("dt", { children: "Chain" }),
1527
+ /* @__PURE__ */ jsx("dd", { children: subject.chain }),
1528
+ /* @__PURE__ */ jsx("dt", { children: "Kind" }),
1529
+ /* @__PURE__ */ jsx("dd", { children: subject.kind }),
1530
+ /* @__PURE__ */ jsx("dt", { children: copy.confidenceLabel }),
1531
+ /* @__PURE__ */ jsxs("dd", { children: [
1532
+ formatConfidence(assessment.confidence),
1533
+ " \u2014 ",
1534
+ copy.confidenceCaveat
1535
+ ] }),
1536
+ /* @__PURE__ */ jsx("dt", { children: copy.modelLabel }),
1537
+ /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx(Mono, { children: assessment.modelVersion }) }),
1538
+ /* @__PURE__ */ jsx("dt", { children: copy.assessedLabel }),
1539
+ /* @__PURE__ */ jsx("dd", { children: formatTimestamp(assessment.assessedAt) })
1540
+ ] }),
1541
+ /* @__PURE__ */ jsx(AvailabilityNote, { assessment }),
1542
+ /* @__PURE__ */ jsx(Section, { title: "Why this level", children: /* @__PURE__ */ jsx(ExplanationList, { explanation: assessment.explanation }) }),
1543
+ sections.isEmpty ? /* @__PURE__ */ jsx(EmptyNote, { children: sections.emptyStatement }) : null,
1544
+ /* @__PURE__ */ jsx(Section, { title: copy.signalsHeading, children: /* @__PURE__ */ jsx(
1545
+ SignalList,
1546
+ {
1547
+ signals: [...sections.activeSignals, ...sections.inertSignals],
1548
+ copy,
1549
+ subjectNoun,
1550
+ showEvidence: true
1551
+ }
1552
+ ) }),
1553
+ /* @__PURE__ */ jsx(Section, { title: copy.capabilitiesHeading, note: copy.capabilitiesAreNotFindings, children: sections.capabilities.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: copy.noCapabilities }) : /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: sections.capabilities.map((capability, index) => /* @__PURE__ */ jsx(CapabilityRow, { capability, copy }, `${capability.type}:${index}`)) }) }),
1554
+ /* @__PURE__ */ jsx(Section, { title: copy.factsHeading, note: copy.factsAreNotFindings, children: sections.facts.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: copy.noFacts }) : /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: sections.facts.map((fact, index) => /* @__PURE__ */ jsx(FactRow, { fact, copy }, `${fact.type}:${index}`)) }) }),
1555
+ /* @__PURE__ */ jsx(Section, { title: copy.unknownsHeading, note: copy.unknownsAreNotRisk, children: sections.unknowns.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: copy.noUnknowns }) : /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: sections.unknowns.map((unknown) => /* @__PURE__ */ jsx(UnknownRow, { unknown, copy }, unknown.field)) }) }),
1556
+ /* @__PURE__ */ jsx(Section, { title: copy.historyHeading, children: /* @__PURE__ */ jsx(HistoryNote, { status: assessment.historyStatus, copy }) }),
1557
+ /* @__PURE__ */ jsx(Section, { title: copy.componentsHeading, children: /* @__PURE__ */ jsx("dl", { className: "dcy-kv", children: componentRows(assessment.components, copy).map((row) => /* @__PURE__ */ jsx(ComponentEntry, { label: row.label, value: row.value, caveat: row.caveat }, row.key)) }) }),
1558
+ /* @__PURE__ */ jsx(DemotedNote, { demotedSignals, copy }),
1559
+ /* @__PURE__ */ jsx("hr", { className: "dcy-hr" }),
1560
+ /* @__PURE__ */ jsxs("p", { className: "dcy-note", "data-dcy-decision": policy.action, children: [
1561
+ /* @__PURE__ */ jsx("span", { className: "dcy-chip", children: policy.label }),
1562
+ " ",
1563
+ policy.statement,
1564
+ " ",
1565
+ copy.policyIsYours
1566
+ ] }),
1567
+ cached ? /* @__PURE__ */ jsx("p", { className: "dcy-note", children: copy.cachedNotice }) : null
1568
+ ]
1569
+ }
1570
+ )
1571
+ ]
1572
+ }
1573
+ );
1574
+ if (portal && typeof document !== "undefined") {
1575
+ return createPortal(dialog, document.body);
1576
+ }
1577
+ return dialog;
1578
+ }
1579
+ function ComponentEntry({
1580
+ label,
1581
+ value,
1582
+ caveat
1583
+ }) {
1584
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
1585
+ /* @__PURE__ */ jsx("dt", { children: label }),
1586
+ /* @__PURE__ */ jsxs("dd", { children: [
1587
+ Math.round(value),
1588
+ " / 100",
1589
+ caveat ? /* @__PURE__ */ jsxs("span", { className: "dcy-note", children: [
1590
+ " ",
1591
+ caveat
1592
+ ] }) : null
1593
+ ] })
1594
+ ] });
1595
+ }
1596
+
1597
+ // src/components/TokenSecurityPanel.tsx
1598
+ function TokenSecurityPanel({
1599
+ result,
1600
+ tokenName,
1601
+ tokenSymbol,
1602
+ onViewDetails,
1603
+ showComponents = true,
1604
+ className,
1605
+ style,
1606
+ theme,
1607
+ copy: copyOverrides,
1608
+ levelLabels
1609
+ }) {
1610
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1611
+ const { assessment, subject, decision, cached, demotedSignals } = result;
1612
+ const level = levelPresentation(assessment.riskLevel, { labels: levelLabels });
1613
+ const sections = assessmentSections(assessment, copy);
1614
+ const policy = decisionPresentation(decision);
1615
+ return /* @__PURE__ */ jsxs(
1616
+ "section",
1617
+ {
1618
+ className: cx("dcy", "dcy-card", className),
1619
+ style: themeStyle(mergedTheme, style),
1620
+ "data-dcy-component": "token-security-panel",
1621
+ "data-dcy-level": assessment.riskLevel,
1622
+ "data-dcy-tone": level.tone,
1623
+ "aria-label": level.ariaLabel,
1624
+ children: [
1625
+ /* @__PURE__ */ jsx(StyleGate, { emit: emitStyles }),
1626
+ /* @__PURE__ */ jsx(LevelHeader, { assessment, copy, levelLabels }),
1627
+ /* @__PURE__ */ jsxs("p", { className: "dcy-note", children: [
1628
+ tokenName || tokenSymbol ? /* @__PURE__ */ jsxs(Fragment2, { children: [
1629
+ /* @__PURE__ */ jsx(Chip, { children: tokenSymbol ?? tokenName }),
1630
+ " ",
1631
+ tokenName && tokenSymbol ? `${tokenName} \u2014 ` : null
1632
+ ] }) : null,
1633
+ /* @__PURE__ */ jsx(Mono, { children: subject.identifier }),
1634
+ " on ",
1635
+ subject.chain
1636
+ ] }),
1637
+ /* @__PURE__ */ jsx(AvailabilityNote, { assessment }),
1638
+ /* @__PURE__ */ jsx(ExplanationList, { explanation: assessment.explanation }),
1639
+ sections.isEmpty ? /* @__PURE__ */ jsx(EmptyNote, { children: sections.emptyStatement }) : null,
1640
+ /* @__PURE__ */ jsx(Section, { title: copy.capabilitiesHeading, note: copy.capabilitiesAreNotFindings, children: sections.capabilities.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: copy.noCapabilities }) : /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: sections.capabilities.map((capability, index) => /* @__PURE__ */ jsx(CapabilityRow, { capability, copy }, `${capability.type}:${index}`)) }) }),
1641
+ /* @__PURE__ */ jsx(Section, { title: copy.factsHeading, note: copy.factsAreNotFindings, children: sections.facts.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: copy.noFacts }) : /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: sections.facts.map((fact, index) => /* @__PURE__ */ jsx(FactRow, { fact, copy }, `${fact.type}:${index}`)) }) }),
1642
+ /* @__PURE__ */ jsx(Section, { title: copy.signalsHeading, children: /* @__PURE__ */ jsx(
1643
+ SignalList,
1644
+ {
1645
+ signals: [...sections.activeSignals, ...sections.inertSignals],
1646
+ copy,
1647
+ subjectNoun: "token"
1648
+ }
1649
+ ) }),
1650
+ /* @__PURE__ */ jsx(Section, { title: copy.unknownsHeading, note: copy.unknownsAreNotRisk, children: sections.unknowns.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: copy.noUnknowns }) : /* @__PURE__ */ jsx("ul", { className: "dcy-list", children: sections.unknowns.map((unknown) => /* @__PURE__ */ jsx(UnknownRow, { unknown, copy }, unknown.field)) }) }),
1651
+ /* @__PURE__ */ jsx(HistoryNote, { status: assessment.historyStatus, copy }),
1652
+ showComponents ? /* @__PURE__ */ jsx(Section, { title: copy.componentsHeading, children: /* @__PURE__ */ jsx("dl", { className: "dcy-kv", children: componentRows(assessment.components, copy).map((row) => /* @__PURE__ */ jsx(ComponentEntry2, { label: row.label, value: row.value, caveat: row.caveat }, row.key)) }) }) : null,
1653
+ /* @__PURE__ */ jsx(DemotedNote, { demotedSignals, copy }),
1654
+ /* @__PURE__ */ jsxs("p", { className: "dcy-note", "data-dcy-decision": policy.action, children: [
1655
+ /* @__PURE__ */ jsx("span", { className: "dcy-chip", children: policy.label }),
1656
+ " ",
1657
+ policy.statement
1658
+ ] }),
1659
+ cached ? /* @__PURE__ */ jsx("p", { className: "dcy-note", children: copy.cachedNotice }) : null,
1660
+ onViewDetails ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("button", { type: "button", className: "dcy-btn dcy-btn-quiet", onClick: onViewDetails, children: copy.viewDetails }) }) : null
1661
+ ]
1662
+ }
1663
+ );
1664
+ }
1665
+ function ComponentEntry2({
1666
+ label,
1667
+ value,
1668
+ caveat
1669
+ }) {
1670
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
1671
+ /* @__PURE__ */ jsx("dt", { children: label }),
1672
+ /* @__PURE__ */ jsxs("dd", { children: [
1673
+ Math.round(value),
1674
+ " / 100",
1675
+ caveat ? /* @__PURE__ */ jsxs("span", { className: "dcy-note", children: [
1676
+ " ",
1677
+ caveat
1678
+ ] }) : null
1679
+ ] })
1680
+ ] });
1681
+ }
1682
+
1683
+ // src/components/AddressRiskBadge.tsx
1684
+ function AddressRiskBadge(props) {
1685
+ const { assessment, level, address, onSelect, className, style, theme, copy, levelLabels } = props;
1686
+ const { theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy });
1687
+ const resolved = assessment?.riskLevel ?? level;
1688
+ if (!resolved) return null;
1689
+ const presentation = levelPresentation(resolved, { labels: levelLabels });
1690
+ const rootClass = cx("dcy", "dcy-inline", className);
1691
+ const rootStyle = themeStyle(mergedTheme, style);
1692
+ const content = /* @__PURE__ */ jsxs(Fragment2, { children: [
1693
+ /* @__PURE__ */ jsx(LevelBadge, { level: presentation }),
1694
+ address ? /* @__PURE__ */ jsx(Mono, { children: shortenAddress(address) }) : null,
1695
+ /* @__PURE__ */ jsx(ScreenReaderText, { children: presentation.meaning })
1696
+ ] });
1697
+ if (onSelect) {
1698
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
1699
+ /* @__PURE__ */ jsx(StyleGate, { emit: emitStyles }),
1700
+ /* @__PURE__ */ jsx(
1701
+ "button",
1702
+ {
1703
+ type: "button",
1704
+ className: rootClass,
1705
+ style: rootStyle,
1706
+ title: presentation.meaning,
1707
+ onClick: onSelect,
1708
+ "data-dcy-component": "address-risk-badge",
1709
+ "data-dcy-level": resolved,
1710
+ "data-dcy-tone": presentation.tone,
1711
+ children: content
1712
+ }
1713
+ )
1714
+ ] });
1715
+ }
1716
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
1717
+ /* @__PURE__ */ jsx(StyleGate, { emit: emitStyles }),
1718
+ /* @__PURE__ */ jsx(
1719
+ "span",
1720
+ {
1721
+ className: rootClass,
1722
+ style: rootStyle,
1723
+ title: presentation.meaning,
1724
+ "data-dcy-component": "address-risk-badge",
1725
+ "data-dcy-level": resolved,
1726
+ "data-dcy-tone": presentation.tone,
1727
+ children: content
1728
+ }
1729
+ )
1730
+ ] });
1731
+ }
1732
+ return __toCommonJS(index_exports);
1733
+ })();