2 Commits
Author SHA1 Message Date
zemion 40ea8d1dab fix(ui): align evidence and assurance heading help
Verified with the coordinated workspace changes by devkit full run
2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed).
This shared UI pass does not mark the individual module reviews complete.
2026-09-09 02:04:14 +02:00
zemion 692ffe80bb Release govoplan-risk-compliance v0.1.21: unify interface contracts and documentation
Module Package Release / publish-packages (push) Successful in 10s
2026-09-08 01:32:52 +02:00
8 changed files with 204 additions and 575 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": "webui/src/index.ts",
@@ -19,7 +19,7 @@
"LICENSE"
],
"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",
+2 -2
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "govoplan-risk-compliance"
version = "0.1.20"
version = "0.1.21"
description = "GovOPlaN Risk Compliance platform module seed."
readme = "README.md"
requires-python = ">=3.12"
@@ -12,7 +12,7 @@ license = { file = "LICENSE" }
authors = [{ name = "GovOPlaN" }]
dependencies = [
"defusedxml>=0.7.1",
"govoplan-core>=0.1.37",
"govoplan-core>=0.1.45",
"govoplan-access>=0.1.18",
]
@@ -69,7 +69,7 @@ from govoplan_risk_compliance.backend.permissions import (
MODULE_ID = "risk_compliance"
MODULE_NAME = "Risk Compliance"
MODULE_VERSION = "0.1.20"
MODULE_VERSION = "0.1.21"
OPTIONAL_DEPENDENCIES = (
"audit",
"policy",
@@ -670,6 +670,28 @@ manifest = ModuleManifest(
),
),
documentation=(
DocumentationTopic(
id="risk_compliance.workspace-layout",
title="Risk Compliance workspace layout",
summary="Find workspace actions and read consistently arranged content.",
body="Documentation books sit immediately beside the visible heading or contextual label for Risk "
"& Compliance and its evidence and assurance sections or dialogs, not among operational "
"action buttons. Field help remains beside its label. "
"Sources, screening, review, and assurance use the same workspace action bar, cards, typography, metrics, and responsive columns as the rest of GovOPlaN. Reload is at the upper right; source imports, dispositions, and assurance edits remain with their relevant card. Narrow windows stack the cards instead of requiring a separate miniature interface. Reloading does not import a source, execute a screening, or record a disposition. Administrators continue to grant the separate read, screening, review, and administration permissions; the shared presentation does not change legal assurance or review policy.",
layer="static",
documentation_types=("user", "admin"),
audience=("user", "module_admin", "operator"),
order=5,
translations={"de": {
"title": "Risiko und Compliance: Aufbau des Arbeitsbereichs",
"summary": "Arbeitsbereichsaktionen finden und einheitlich angeordnete Inhalte lesen.",
"body": "Dokumentationsbücher stehen unmittelbar neben der sichtbaren Überschrift oder "
"Kontextbezeichnung für Risiko & Compliance und die zugehörigen Nachweis- und "
"Absicherungsabschnitte oder Dialoge, nicht zwischen ausführbaren Aktionsschaltflächen. "
"Feldhilfe bleibt neben der Feldbezeichnung. "
"Quellen, Screening, Prüfung und Assurance verwenden dieselbe Arbeitsbereichsleiste, Karten, Typografie, Kennzahlen und responsiven Spalten wie das übrige GovOPlaN. Neu laden steht oben rechts; Quellenimporte, Prüfentscheidungen und Assurance-Bearbeitung bleiben bei ihrer jeweiligen Karte. Schmale Fenster ordnen die Karten untereinander an, statt eine verkleinerte Sonderoberfläche zu verwenden. Neu laden importiert keine Quelle, startet kein Screening und erfasst keine Entscheidung. Administratoren vergeben weiterhin getrennte Lese-, Screening-, Prüf- und Administrationsrechte; die gemeinsame Darstellung verändert weder rechtliche Zusicherungen noch Prüfrichtlinien.",
}},
),
DocumentationTopic(
id="risk_compliance.data-subject-requests",
title="Risk and compliance data-subject requests",
+16 -1
View File
@@ -11,7 +11,22 @@ from govoplan_risk_compliance.backend.manifest import manifest
class RiskComplianceDocumentationTests(unittest.TestCase):
def test_public_topics_have_complete_german_reference_content(self) -> None:
self.assertEqual(2, len(manifest.documentation))
topics = {topic.id: topic for topic in manifest.documentation}
self.assertEqual(
len(manifest.documentation), len(topics), "Documentation topic IDs must be unique"
)
self.assertLessEqual(
{
"risk_compliance.module-boundary",
"risk_compliance.data-subject-requests",
"risk_compliance.workspace-layout",
},
set(topics),
)
self.assertLessEqual(
{"user", "admin"},
set(topics["risk_compliance.workspace-layout"].documentation_types),
)
for topic in manifest.documentation:
translation = topic.translations.get("de", {})
self.assertTrue(
+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,16 @@ 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 }}
title="Risk Compliance"
titleLevel={1}
titleHelp={<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />}
contextActions={<SegmentedControl
value={view}
onChange={setView}
ariaLabel="Risk Compliance view"
@@ -282,7 +294,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 +313,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 +331,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 +386,8 @@ export default function RiskCompliancePage({
onRefresh={refresh}
/>
)}
</div>
</ContentGrid>
</PageScrollViewport>
<ConfirmDialog
open={Boolean(pendingImport)}
title="i18n:govoplan-risk-compliance.import_snapshot_title"
@@ -398,7 +400,7 @@ export default function RiskCompliancePage({
onCancel={() => setPendingImport(null)}
onConfirm={() => pendingImport && void importSnapshot(pendingImport)}
/>
</main>
</WorkspaceFrame>
);
}
@@ -422,15 +424,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" titleHelp={<DocumentationHelpLink reference={RISK_COMPLIANCE_ADMIN_DOCUMENTATION} />} >
{!available && (
<ActionBlockerHint
tone="info"
@@ -472,14 +467,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 +487,8 @@ function SourcesPane({
<StatePanel size="compact" description="No source snapshot has been imported." />
)}
</div>
</div>
</section>
</Card>
</ContentGrid>
);
}
@@ -582,16 +571,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" titleHelp={<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />} >
<form onSubmit={submit}>
<FormGrid columns={1}>
{!snapshots.length && (
<ActionBlockerHint
reason={{
@@ -653,16 +636,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 +670,8 @@ function ScreenPane({
)}
</div>
)}
</div>
</section>
</Card>
</ContentGrid>
);
}
@@ -785,14 +762,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,15 +788,9 @@ 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">
<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />
</Card>
<Card title="Candidate evidence" titleHelp={<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />} actions={<>
<Button
variant="primary"
helpContextId="risk_compliance.review.disposition"
@@ -836,14 +801,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 +833,7 @@ function ReviewPane({
(item) => item.value
)}
/>
</div>
</ContentGrid>
<div className="risk-match-evidence">
<strong>
{detail.candidate.score}% ·{" "}
@@ -886,10 +850,10 @@ function ReviewPane({
</div>
</div>
)}
</div>
</Card>
<Dialog
open={dialogOpen}
title="Record screening disposition"
title="Record screening disposition" titleHelp={<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />}
onClose={resetDisposition}
closeDisabled={busy}
footer={
@@ -918,7 +882,7 @@ function ReviewPane({
className="risk-disposition-form"
onSubmit={submitDisposition}
>
<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />
<ActionBlockerHint
tone="info"
reason={{
@@ -957,7 +921,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 +938,7 @@ function ReviewPane({
)}
</form>
</Dialog>
</section>
</ContentGrid>
);
}
@@ -1220,8 +1184,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,16 +1210,10 @@ 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">
<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />
</ContentGrid>
<ContentGrid columns={2} collapseAt="wide" align="stretch">
<Card title="Assurance objects" titleHelp={<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />} actions={<>
<IconButton
label="Add assurance object"
icon={<Plus size={16} />}
@@ -1263,9 +1221,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 +1240,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,15 +1264,9 @@ 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">
<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />
</Card>
<Card title={selected?.label || "Assurance object"} titleHelp={<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />} actions={<>
<IconButton
label="Edit assurance object"
icon={<Pencil size={16} />}
@@ -1323,23 +1274,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 +1327,8 @@ function AssurancePane({
)}
</div>
)}
</div>
</div>
</Card>
</ContentGrid>
<AssuranceNodeDialog
open={nodeDialogOpen}
busy={busy}
@@ -1390,7 +1341,7 @@ function AssurancePane({
/>
<Dialog
open={edgeDialogOpen}
title="Connect assurance objects"
title="Connect assurance objects" titleHelp={<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />}
onClose={closeEdgeDialog}
closeDisabled={busy}
footer={
@@ -1408,7 +1359,7 @@ function AssurancePane({
</>
}
>
<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />
<form id="risk-assurance-edge-form" className="risk-assurance-form" onSubmit={submitEdge}>
<FormField label="Relationship">
<select
@@ -1438,7 +1389,7 @@ function AssurancePane({
</FormField>
</form>
</Dialog>
</section>
</ContentGrid>
);
}
@@ -1490,7 +1441,7 @@ function AssuranceNodeDialog({
return (
<Dialog
open={open}
title={editing ? "Revise assurance object" : "Add assurance object"}
title={editing ? "Revise assurance object" : "Add assurance object"} titleHelp={<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />}
onClose={onClose}
closeDisabled={busy}
footer={
@@ -1508,7 +1459,7 @@ function AssuranceNodeDialog({
</>
}
>
<DocumentationHelpLink reference={RISK_COMPLIANCE_DOCUMENTATION} />
<form id="risk-assurance-node-form" className="risk-assurance-form" onSubmit={onSubmit}>
<FormGrid columns={2} gap="compact" collapseAt="narrow">
<FormField label="Stable ID">
@@ -1666,7 +1617,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({
+2
View File
@@ -2,6 +2,7 @@ import type { PlatformTranslations } from "@govoplan/core-webui";
export const generatedTranslations: PlatformTranslations = {
en: {
"Risk Compliance": "Risk Compliance",
"i18n:govoplan-risk-compliance.surface.sources": "Sanctions source snapshots",
"i18n:govoplan-risk-compliance.surface.screening": "Sanctions screening",
"i18n:govoplan-risk-compliance.surface.review": "Sanctions review queue",
@@ -53,6 +54,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-risk-compliance.blocker.target": "Where to go"
},
de: {
"Risk Compliance": "Risiko und Compliance",
"i18n:govoplan-risk-compliance.surface.sources": "Sanktionsquellen",
"i18n:govoplan-risk-compliance.surface.screening": "Sanktionsprüfung",
"i18n:govoplan-risk-compliance.surface.review": "Prüfwarteschlange",
+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);
}
}