Release govoplan-risk-compliance v0.1.21: unify interface contracts and documentation
Module Package Release / publish-packages (push) Successful in 10s

This commit is contained in:
2026-09-08 01:32:52 +02:00
parent e898c853e5
commit 692ffe80bb
7 changed files with 184 additions and 566 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@govoplan/risk-compliance-webui",
"version": "0.1.20",
"version": "0.1.21",
"private": true,
"type": "module",
"main": "src/index.ts",
@@ -14,7 +14,7 @@
"./styles/risk-compliance.css": "./src/styles/risk-compliance.css"
},
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"@govoplan/core-webui": "^0.1.45",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -6,7 +6,6 @@ import {
Pencil,
Play,
Plus,
RefreshCw,
Scale,
Upload
} from "lucide-react";
@@ -18,23 +17,29 @@ import {
type FormEvent
} from "react";
import { useSearchParams } from "react-router";
import { FormGrid, ActionToolbar, ToolbarSpacer,
import { FormGrid,
ActionBlockerHint,
Button,
Card,
ConfirmDialog,
ContentGrid,
DescriptionList,
DescriptionItem,
Dialog,
DismissibleAlert,
DocumentationHelpLink,
FormField,
IconButton,
LoadingIndicator,
MetricCard,
PageScrollViewport,
SegmentedControl,
SelectionList,
SelectionListItem,
StatePanel,
StatusBadge,
ToggleSwitch,
WorkspaceActionBar,
WorkspaceFrame,
hasScope,
i18nMessage,
useUnsavedDraftGuard,
@@ -248,9 +253,14 @@ export default function RiskCompliancePage({
}
return (
<main className="risk-page">
<ActionToolbar className="risk-toolbar">
<SegmentedControl
<WorkspaceFrame as="main" surface="plain" label="Risk Compliance workspace" interfaceId="risk_compliance.workspace" helpContextId="risk_compliance.workspace" helpModuleId="risk_compliance">
<WorkspaceActionBar
scope="workspace"
variant="workspace"
refreshable
reloadAction={{ onReload: () => void refresh(), loading, disabled: busy, disabledReason: busy ? RISK_COMPLIANCE_I18N.busy : undefined }}
helpAction={<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />}
contextActions={<SegmentedControl
value={view}
onChange={setView}
ariaLabel="Risk Compliance view"
@@ -282,7 +292,7 @@ export default function RiskCompliancePage({
<Scale size={15} />
Review
{queue.length > 0 && (
<span className="risk-count">{queue.length}</span>
<StatusBadge status="info" label={String(queue.length)} />
)}
</>
),
@@ -301,25 +311,16 @@ export default function RiskCompliancePage({
title: !canReadAssurance ? RISK_COMPLIANCE_I18N.assuranceReadRequired : undefined
}
]}
/>
<ToolbarSpacer className="risk-toolbar-spacer" />
<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />
{loading && <LoadingIndicator size="sm" label="Loading" />}
<IconButton
label="Refresh"
icon={<RefreshCw size={16} />}
onClick={() => void refresh()}
disabled={loading || busy}
disabledReason={loading ? RISK_COMPLIANCE_I18N.loading : busy ? RISK_COMPLIANCE_I18N.busy : undefined}
/>
</ActionToolbar>
/>}
/>
<PageScrollViewport>
<ContentGrid columns={1} className="content-pad">
{(error || notice) && (
<div className="risk-alerts">
{error && (
<DismissibleAlert
tone="danger"
resetKey={error}
onDismiss={() => setError("")}
>
{error}
</DismissibleAlert>
@@ -328,14 +329,12 @@ export default function RiskCompliancePage({
<DismissibleAlert
tone="success"
resetKey={notice}
onDismiss={() => setNotice("")}
>
{notice}
</DismissibleAlert>
)}
</div>
)}
<div className="risk-workspace">
{view === "sources" && (
<SourcesPane
available={sourcesAvailable}
@@ -385,7 +384,8 @@ export default function RiskCompliancePage({
onRefresh={refresh}
/>
)}
</div>
</ContentGrid>
</PageScrollViewport>
<ConfirmDialog
open={Boolean(pendingImport)}
title="i18n:govoplan-risk-compliance.import_snapshot_title"
@@ -398,7 +398,7 @@ export default function RiskCompliancePage({
onCancel={() => setPendingImport(null)}
onConfirm={() => pendingImport && void importSnapshot(pendingImport)}
/>
</main>
</WorkspaceFrame>
);
}
@@ -422,15 +422,8 @@ function SourcesPane({
[imported]
);
return (
<section className="risk-source-layout">
<div className="risk-panel">
<header>
<div>
<strong>Connector evidence</strong>
<span>Immutable acquired source snapshots</span>
</div>
<DocumentationHelpLink reference={RISK_COMPLIANCE_ADMIN_DOCUMENTATION} />
</header>
<ContentGrid columns={2} collapseAt="wide" align="stretch">
<Card title="Connector evidence" actions={<DocumentationHelpLink reference={RISK_COMPLIANCE_ADMIN_DOCUMENTATION} />}>
{!available && (
<ActionBlockerHint
tone="info"
@@ -472,14 +465,8 @@ function SourcesPane({
);
})}
</div>
</div>
<div className="risk-panel">
<header>
<div>
<strong>Screening catalogues</strong>
<span>Normalized, immutable list versions</span>
</div>
</header>
</Card>
<Card title="Screening catalogues">
<div className="risk-list">
{imported.map((item) => (
<div className="risk-list-row" key={item.id}>
@@ -498,8 +485,8 @@ function SourcesPane({
<StatePanel size="compact" description="No source snapshot has been imported." />
)}
</div>
</div>
</section>
</Card>
</ContentGrid>
);
}
@@ -582,16 +569,10 @@ function ScreenPane({
}
return (
<section className="risk-screen-layout">
<form className="risk-panel risk-screen-form" onSubmit={submit}>
<header>
<div>
<strong>New screening</strong>
<span>Use only the data needed for comparison</span>
</div>
<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />
</header>
<div className="risk-form-body">
<ContentGrid columns={2} collapseAt="wide" align="stretch">
<Card title="New screening" actions={<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />}>
<form onSubmit={submit}>
<FormGrid columns={1}>
{!snapshots.length && (
<ActionBlockerHint
reason={{
@@ -653,16 +634,10 @@ function ScreenPane({
<Play size={16} />
Run screening
</Button>
</div>
</FormGrid>
</form>
<div className="risk-panel risk-run-result">
<header>
<div>
<strong>Result</strong>
<span>Version-pinned candidate evidence</span>
</div>
{run && <StatusBadge status={run.outcome} />}
</header>
</Card>
<Card title="Result" actions={run && <StatusBadge status={run.outcome} />}>
{!run && (
<StatePanel size="compact" description="Run a screening to inspect the result." />
)}
@@ -693,8 +668,8 @@ function ScreenPane({
)}
</div>
)}
</div>
</section>
</Card>
</ContentGrid>
);
}
@@ -785,14 +760,8 @@ function ReviewPane({
}
return (
<section className="risk-review-layout">
<aside className="risk-panel risk-review-queue">
<header>
<div>
<strong>Review queue</strong>
<span>{queue.length} candidates need review</span>
</div>
</header>
<ContentGrid columns={2} collapseAt="wide" align="stretch">
<Card title="Review queue" actions={<StatusBadge status="info" label={String(queue.length)} />}>
<div className="risk-list">
{queue.length > 0 && (
<SelectionList label="Review candidates">
@@ -817,14 +786,8 @@ function ReviewPane({
<StatePanel size="compact" description="No candidates currently need review." />
)}
</div>
</aside>
<div className="risk-panel risk-evidence">
<header>
<div>
<strong>Candidate evidence</strong>
<span>Subject and immutable list entry comparison</span>
</div>
<div className="risk-header-actions">
</Card>
<Card title="Candidate evidence" actions={<>
<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />
<Button
variant="primary"
@@ -836,14 +799,13 @@ function ReviewPane({
>
Record disposition
</Button>
</div>
</header>
</>}>
{!detail && (
<StatePanel size="compact" description="Select a candidate from the queue." />
)}
{detail && (
<div className="risk-evidence-body">
<div className="risk-comparison">
<ContentGrid columns={2}>
<EvidenceColumn
title="Screening subject"
name={detail.subject.primary_name || "Identifier-only"}
@@ -869,7 +831,7 @@ function ReviewPane({
(item) => item.value
)}
/>
</div>
</ContentGrid>
<div className="risk-match-evidence">
<strong>
{detail.candidate.score}% ·{" "}
@@ -886,7 +848,7 @@ function ReviewPane({
</div>
</div>
)}
</div>
</Card>
<Dialog
open={dialogOpen}
title="Record screening disposition"
@@ -957,7 +919,7 @@ function ReviewPane({
<>
<ToggleSwitch
checked={reusable}
onChange={(event) => setReusable(event.target.checked)}
onChange={setReusable}
label="Apply as a time-bounded exception to this subject and list entry"
/>
{reusable && (
@@ -974,7 +936,7 @@ function ReviewPane({
)}
</form>
</Dialog>
</section>
</ContentGrid>
);
}
@@ -1220,8 +1182,8 @@ function AssurancePane({
}
return (
<section className="risk-assurance-layout">
<div className="risk-assurance-summary">
<ContentGrid columns={1}>
<ContentGrid columns={1}>
{!canWrite && (
<ActionBlockerHint
tone="info"
@@ -1246,15 +1208,9 @@ function AssurancePane({
tone={(summary?.by_state.open ?? 0) > 0 ? "danger" : "good"}
/>
</MetricGrid>
</div>
<div className="risk-assurance-columns">
<aside className="risk-panel">
<header>
<div>
<strong>Assurance objects</strong>
<span>Current effective revisions</span>
</div>
<div className="risk-header-actions">
</ContentGrid>
<ContentGrid columns={2} collapseAt="wide" align="stretch">
<Card title="Assurance objects" actions={<>
<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />
<IconButton
label="Add assurance object"
@@ -1263,9 +1219,8 @@ function AssurancePane({
disabled={!canWrite || busy}
disabledReason={!canWrite ? RISK_COMPLIANCE_I18N.assuranceWriteRequired : busy ? RISK_COMPLIANCE_I18N.busy : undefined}
/>
</div>
</header>
<div className="risk-assurance-filter">
</>}>
<FormGrid columns={2} gap="small" spacing="block">
<input
type="search"
value={query}
@@ -1283,7 +1238,7 @@ function AssurancePane({
<option value={item} key={item}>{formatToken(item)}</option>
))}
</select>
</div>
</FormGrid>
<div className="risk-list">
{visibleNodes.length > 0 && (
<SelectionList label="Assurance objects">
@@ -1307,14 +1262,8 @@ function AssurancePane({
<StatePanel size="compact" description="No assurance objects match." />
)}
</div>
</aside>
<div className="risk-panel risk-assurance-detail">
<header>
<div>
<strong>{selected?.label || "Assurance object"}</strong>
<span>{selected ? formatToken(selected.kind) : "Select an object"}</span>
</div>
<div className="risk-header-actions">
</Card>
<Card title={selected?.label || "Assurance object"} actions={<>
<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />
<IconButton
label="Edit assurance object"
@@ -1323,23 +1272,23 @@ function AssurancePane({
disabled={!selected || !canWrite || selected.stable_id.startsWith("sanctions-") || busy}
disabledReason={!selected ? RISK_COMPLIANCE_I18N.assuranceObjectRequired : !canWrite ? RISK_COMPLIANCE_I18N.assuranceWriteRequired : selected.stable_id.startsWith("sanctions-") ? RISK_COMPLIANCE_I18N.systemManagedObject : busy ? RISK_COMPLIANCE_I18N.busy : undefined}
/>
</div>
</header>
</>}>
{!selected && (
<StatePanel size="compact" description="Select an assurance object." />
)}
{selected && (
<div className="risk-assurance-detail-body">
<dl className="risk-assurance-properties">
<div><dt>State</dt><dd><StatusBadge status={selected.state} /></dd></div>
<div><dt>Owner</dt><dd>{selected.owner_ref}</dd></div>
<div><dt>Scope</dt><dd>{selected.scope_ref || "Tenant"}</dd></div>
<div><dt>Valid from</dt><dd>{formatDate(selected.valid_from)}</dd></div>
<DescriptionList columns={2}>
<DescriptionItem term="Type">{formatToken(selected.kind)}</DescriptionItem>
<DescriptionItem term="State"><StatusBadge status={selected.state} /></DescriptionItem>
<DescriptionItem term="Owner">{selected.owner_ref}</DescriptionItem>
<DescriptionItem term="Scope">{selected.scope_ref || "Tenant"}</DescriptionItem>
<DescriptionItem term="Valid from">{formatDate(selected.valid_from)}</DescriptionItem>
{selected.governed_object_ref && (
<div><dt>Governed object</dt><dd><code>{selected.governed_object_ref}</code></dd></div>
<DescriptionItem term="Governed object"><code>{selected.governed_object_ref}</code></DescriptionItem>
)}
<div><dt>Classification</dt><dd>{selected.classification}</dd></div>
</dl>
<DescriptionItem term="Classification">{selected.classification}</DescriptionItem>
</DescriptionList>
{selected.description && <p>{selected.description}</p>}
<div className="risk-assurance-links-header">
<div>
@@ -1376,8 +1325,8 @@ function AssurancePane({
)}
</div>
)}
</div>
</div>
</Card>
</ContentGrid>
<AssuranceNodeDialog
open={nodeDialogOpen}
busy={busy}
@@ -1438,7 +1387,7 @@ function AssurancePane({
</FormField>
</form>
</Dialog>
</section>
</ContentGrid>
);
}
@@ -1666,7 +1615,7 @@ function dateTimeLocalValue(value: Date) {
}
function formatToken(value: string) {
return value.replaceAll("_", " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
return value.replace(/_/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
}
function EvidenceColumn({
+73 -434
View File
@@ -1,194 +1,114 @@
.risk-page {
display: flex;
/* Domain-specific evidence arrangements only. Workspace geometry, cards,
headings, controls, metrics and responsive columns belong to Core. */
.risk-list-main,
.risk-candidate-summary > div,
.risk-evidence-column,
.risk-match-evidence,
.risk-evidence-values {
display: grid;
min-width: 0;
min-height: 0;
height: 100%;
flex-direction: column;
background: var(--panel);
gap: var(--space-1);
}
.risk-toolbar {
.risk-list-main,
.risk-candidate-summary > div {
flex: 1;
}
.risk-list-row,
.risk-candidate-summary,
.risk-clear,
.risk-assurance-links-header {
display: flex;
align-items: center;
gap: 10px;
flex: 0 0 auto;
min-height: 50px;
gap: var(--space-2);
padding-block: var(--space-3);
}
.risk-list-row,
.risk-candidate-summary,
.risk-assurance-links-header {
border-bottom: var(--border-line);
background: var(--panel-header);
padding: 8px 14px;
}
.risk-toolbar .segmented-control-option {
gap: 6px;
.risk-list-main strong,
.risk-list-main span,
.risk-list-main code {
overflow-wrap: anywhere;
}
.risk-toolbar-spacer {
flex: 1;
.risk-list-main span,
.risk-list-main code,
.risk-candidate-summary > div > span,
.risk-evidence-column > span,
.risk-evidence-values span,
.risk-match-evidence span,
.risk-match-evidence code,
.risk-assurance-links-header span {
color: var(--muted);
}
.risk-count {
min-width: 18px;
border-radius: var(--radius-md);
background: var(--accent);
color: var(--on-dark);
padding: 1px 5px;
font-size: 10px;
text-align: center;
.risk-evidence-values strong {
overflow-wrap: anywhere;
font-weight: 500;
}
.risk-alerts {
.risk-score {
display: inline-grid;
min-width: 2.5em;
flex: 0 0 auto;
padding: 10px 14px 0;
place-items: center;
padding: var(--space-1);
border: 1px solid var(--warning-border);
border-radius: var(--radius-sm);
background: var(--warning-soft);
color: var(--text-strong);
font-weight: 700;
}
.risk-workspace {
min-width: 0;
min-height: 0;
flex: 1;
overflow: hidden;
padding: 14px;
.risk-clear {
color: var(--success);
}
.risk-source-layout,
.risk-screen-layout,
.risk-review-layout,
.risk-assurance-layout {
.risk-evidence-values,
.risk-match-evidence {
margin-block-start: var(--space-3);
}
.risk-assurance-detail-body,
.risk-assurance-form,
.risk-disposition-form {
display: grid;
min-width: 0;
min-height: 0;
height: 100%;
gap: 12px;
}
.risk-source-layout,
.risk-screen-layout {
grid-template-columns: minmax(320px, 1fr) minmax(360px, 1.35fr);
}
.risk-review-layout {
grid-template-columns: minmax(300px, 0.7fr) minmax(480px, 1.6fr);
}
.risk-assurance-layout {
grid-template-rows: auto minmax(0, 1fr);
}
.risk-assurance-summary {
display: grid;
min-width: 0;
gap: 10px;
}
.risk-assurance-columns {
display: grid;
min-width: 0;
min-height: 0;
grid-template-columns: minmax(300px, 0.75fr) minmax(480px, 1.55fr);
gap: 12px;
}
.risk-assurance-filter {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(120px, 0.45fr);
gap: 8px;
border-bottom: var(--border-line);
padding: 8px 10px;
}
.risk-assurance-filter input,
.risk-assurance-filter select,
.risk-assurance-form input,
.risk-assurance-form select,
.risk-assurance-form textarea {
width: 100%;
box-sizing: border-box;
}
.risk-assurance-detail-body {
display: flex;
min-height: 0;
flex: 1;
flex-direction: column;
overflow: auto;
gap: var(--space-3);
}
.risk-assurance-detail-body > p {
margin: 0;
border-bottom: var(--border-line);
color: var(--text);
padding: 12px;
line-height: 1.5;
}
.risk-assurance-properties {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
margin: 0;
border-bottom: var(--border-line);
}
.risk-assurance-properties > div {
min-width: 0;
border-right: var(--border-line);
border-bottom: var(--border-line);
padding: 10px 12px;
}
.risk-assurance-properties > div:nth-child(2n) {
border-right: 0;
}
.risk-assurance-properties dt {
margin-bottom: 4px;
color: var(--muted);
font-size: 10px;
text-transform: uppercase;
}
.risk-assurance-properties dd {
min-width: 0;
margin: 0;
overflow-wrap: anywhere;
color: var(--text-strong);
font-size: 12px;
}
.risk-assurance-links-header {
display: flex;
align-items: center;
gap: 10px;
min-height: 52px;
border-bottom: var(--border-line);
padding: 8px 12px;
flex-wrap: wrap;
}
.risk-assurance-links-header > div {
display: grid;
gap: 2px;
flex: 1;
}
.risk-assurance-links-header span {
color: var(--muted);
font-size: 11px;
}
.risk-assurance-links {
min-height: 0;
overflow: auto;
gap: var(--space-1);
}
.risk-assurance-links > button {
display: grid;
display: flex;
flex-wrap: wrap;
width: 100%;
grid-template-columns: minmax(110px, 0.45fr) minmax(0, 1fr) auto;
align-items: center;
gap: 10px;
gap: var(--space-2);
border: 0;
border-bottom: var(--border-line);
background: transparent;
color: var(--text);
padding: 9px 12px;
padding: var(--space-3) var(--space-1);
text-align: left;
font: inherit;
cursor: pointer;
@@ -198,296 +118,15 @@
background: var(--sidebar-hover-bg);
}
.risk-assurance-links > button span {
color: var(--muted);
font-size: 11px;
.risk-assurance-links > button strong {
flex: 1;
overflow-wrap: anywhere;
}
.risk-assurance-links > button strong {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
.risk-assurance-links > button span {
color: var(--muted);
}
.risk-assurance-truncated {
border-top: var(--border-line);
color: var(--warning);
padding: 9px 12px;
font-size: 11px;
}
.risk-assurance-form {
display: grid;
gap: 12px;
}
.risk-panel {
display: flex;
min-width: 0;
min-height: 0;
flex-direction: column;
border: var(--border-line);
border-radius: var(--radius-compact);
background: var(--surface);
overflow: hidden;
}
.risk-panel > header {
display: flex;
align-items: center;
gap: 10px;
min-height: 56px;
flex: 0 0 auto;
border-bottom: var(--border-line);
background: var(--panel-header);
padding: 9px 12px;
}
.risk-panel > header > div:first-child {
display: grid;
min-width: 0;
gap: 2px;
flex: 1;
}
.risk-panel > header strong {
color: var(--text-strong);
font-size: 13px;
}
.risk-panel > header span {
color: var(--muted);
font-size: 11px;
}
.risk-header-actions {
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
}
.risk-list,
.risk-result-body,
.risk-evidence-body {
min-height: 0;
flex: 1;
overflow: auto;
}
.risk-list-row,
.risk-candidate-summary {
display: flex;
align-items: center;
gap: 10px;
min-height: 58px;
border: 0;
border-bottom: var(--border-line);
background: transparent;
color: var(--text);
padding: 8px 11px;
}
.risk-queue-row {
display: flex;
min-height: 58px;
align-items: center;
gap: 10px;
}
.risk-list > .selection-list {
gap: 2px;
padding: 4px;
}
.risk-list-main {
display: grid;
min-width: 0;
gap: 3px;
flex: 1;
}
.risk-list-main strong,
.risk-list-main span,
.risk-list-main code {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.risk-list-main strong {
color: var(--text-strong);
font-size: 13px;
}
.risk-list-main span,
.risk-list-main code {
color: var(--muted);
font-size: 11px;
}
.risk-form-body {
display: grid;
align-content: start;
gap: 14px;
overflow: auto;
padding: 16px;
}
.risk-form-body input,
.risk-form-body select,
.risk-disposition-form input,
.risk-disposition-form select,
.risk-disposition-form textarea {
width: 100%;
box-sizing: border-box;
}
.risk-form-body .btn {
justify-self: start;
}
.risk-score {
display: inline-grid;
width: 38px;
height: 32px;
flex: 0 0 38px;
place-items: center;
border: 1px solid var(--warning-border);
border-radius: var(--radius-sm);
background: var(--warning-soft);
color: var(--text-strong);
font-weight: 700;
}
.risk-candidate-summary > div {
display: grid;
min-width: 0;
gap: 3px;
flex: 1;
}
.risk-candidate-summary span {
color: var(--muted);
font-size: 11px;
}
.risk-clear {
display: flex;
align-items: center;
gap: 8px;
color: var(--success);
padding: 18px;
}
.risk-comparison {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
border-bottom: var(--border-line);
}
.risk-evidence-column {
display: grid;
align-content: start;
gap: 5px;
min-width: 0;
border-right: var(--border-line);
padding: 16px;
}
.risk-evidence-column:last-child {
border-right: 0;
}
.risk-evidence-column > strong {
color: var(--text-strong);
font-size: 16px;
}
.risk-evidence-column > span {
color: var(--muted);
font-size: 12px;
}
.risk-eyebrow {
text-transform: uppercase;
font-size: 10px !important;
font-weight: 700;
}
.risk-evidence-values {
display: grid;
gap: 3px;
margin-top: 10px;
}
.risk-evidence-values span {
color: var(--muted);
font-size: 10px;
text-transform: uppercase;
}
.risk-evidence-values strong {
overflow-wrap: anywhere;
color: var(--text);
font-size: 12px;
font-weight: 500;
}
.risk-match-evidence {
display: grid;
gap: 6px;
padding: 16px;
}
.risk-match-evidence span,
.risk-match-evidence code {
color: var(--muted);
font-size: 11px;
}
.risk-disposition-form {
display: grid;
gap: 14px;
}
@media (max-width: 900px) {
.risk-workspace {
overflow: auto;
}
.risk-source-layout,
.risk-screen-layout,
.risk-review-layout,
.risk-assurance-layout,
.risk-assurance-columns {
height: auto;
grid-template-columns: minmax(0, 1fr);
}
.risk-assurance-metrics {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.risk-assurance-properties {
grid-template-columns: minmax(0, 1fr);
}
.risk-assurance-properties > div {
border-right: 0;
}
.risk-panel {
min-height: 340px;
}
.risk-comparison {
grid-template-columns: minmax(0, 1fr);
}
.risk-evidence-column {
border-right: 0;
border-bottom: var(--border-line);
}
}