From 692ffe80bb6994e41f9a3cf152e2eb669db4a2a2 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Tue, 8 Sep 2026 01:32:52 +0200 Subject: [PATCH] Release govoplan-risk-compliance v0.1.21: unify interface contracts and documentation --- package.json | 4 +- pyproject.toml | 4 +- .../backend/manifest.py | 17 +- tests/test_documentation.py | 17 +- webui/package.json | 4 +- .../riskCompliance/RiskCompliancePage.tsx | 197 +++---- webui/src/styles/risk-compliance.css | 507 +++--------------- 7 files changed, 184 insertions(+), 566 deletions(-) diff --git a/package.json b/package.json index 1cc3839..626a964 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pyproject.toml b/pyproject.toml index 89f07bd..2e2e414 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", ] diff --git a/src/govoplan_risk_compliance/backend/manifest.py b/src/govoplan_risk_compliance/backend/manifest.py index 7fb3c6f..57e1f18 100644 --- a/src/govoplan_risk_compliance/backend/manifest.py +++ b/src/govoplan_risk_compliance/backend/manifest.py @@ -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,21 @@ manifest = ModuleManifest( ), ), documentation=( + DocumentationTopic( + id="risk_compliance.workspace-layout", + title="Risk Compliance workspace layout", + summary="Find workspace actions and read consistently arranged content.", + body="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": "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", diff --git a/tests/test_documentation.py b/tests/test_documentation.py index 347a023..fb06c99 100644 --- a/tests/test_documentation.py +++ b/tests/test_documentation.py @@ -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( diff --git a/webui/package.json b/webui/package.json index 7c4033b..849ed3d 100644 --- a/webui/package.json +++ b/webui/package.json @@ -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", diff --git a/webui/src/features/riskCompliance/RiskCompliancePage.tsx b/webui/src/features/riskCompliance/RiskCompliancePage.tsx index c032a94..12c6828 100644 --- a/webui/src/features/riskCompliance/RiskCompliancePage.tsx +++ b/webui/src/features/riskCompliance/RiskCompliancePage.tsx @@ -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 ( -
- - + void refresh(), loading, disabled: busy, disabledReason: busy ? RISK_COMPLIANCE_I18N.busy : undefined }} + helpAction={} + contextActions={ Review {queue.length > 0 && ( - {queue.length} + )} ), @@ -301,25 +311,16 @@ export default function RiskCompliancePage({ title: !canReadAssurance ? RISK_COMPLIANCE_I18N.assuranceReadRequired : undefined } ]} - /> - - - {loading && } - } - onClick={() => void refresh()} - disabled={loading || busy} - disabledReason={loading ? RISK_COMPLIANCE_I18N.loading : busy ? RISK_COMPLIANCE_I18N.busy : undefined} - /> - + />} + /> + + {(error || notice) && (
{error && ( setError("")} > {error} @@ -328,14 +329,12 @@ export default function RiskCompliancePage({ setNotice("")} > {notice} )}
)} -
{view === "sources" && ( )} -
+
+
setPendingImport(null)} onConfirm={() => pendingImport && void importSnapshot(pendingImport)} /> -
+ ); } @@ -422,15 +422,8 @@ function SourcesPane({ [imported] ); return ( -
-
-
-
- Connector evidence - Immutable acquired source snapshots -
- -
+ + }> {!available && ( -
-
-
-
- Screening catalogues - Normalized, immutable list versions -
-
+ +
{imported.map((item) => (
@@ -498,8 +485,8 @@ function SourcesPane({ )}
-
-
+ + ); } @@ -582,16 +569,10 @@ function ScreenPane({ } return ( -
-
-
-
- New screening - Use only the data needed for comparison -
- -
-
+ + }> + + {!snapshots.length && ( Run screening -
+
-
-
-
- Result - Version-pinned candidate evidence -
- {run && } -
+ + }> {!run && ( )} @@ -693,8 +668,8 @@ function ScreenPane({ )}
)} - -
+ + ); } @@ -785,14 +760,8 @@ function ReviewPane({ } return ( -
- -
-
-
- Candidate evidence - Subject and immutable list entry comparison -
-
+ + -
-
+ }> {!detail && ( )} {detail && (
-
+ item.value )} /> -
+
{detail.candidate.score}% ·{" "} @@ -886,7 +848,7 @@ function ReviewPane({
)} -
+ 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({ )} -
+ ); } @@ -1220,8 +1182,8 @@ function AssurancePane({ } return ( -
-
+ + {!canWrite && ( 0 ? "danger" : "good"} /> -
-
- -
-
-
- {selected?.label || "Assurance object"} - {selected ? formatToken(selected.kind) : "Select an object"} -
-
+ + -
-
+ }> {!selected && ( )} {selected && (
-
-
State
-
Owner
{selected.owner_ref}
-
Scope
{selected.scope_ref || "Tenant"}
-
Valid from
{formatDate(selected.valid_from)}
+ + {formatToken(selected.kind)} + + {selected.owner_ref} + {selected.scope_ref || "Tenant"} + {formatDate(selected.valid_from)} {selected.governed_object_ref && ( -
Governed object
{selected.governed_object_ref}
+ {selected.governed_object_ref} )} -
Classification
{selected.classification}
-
+ {selected.classification} + {selected.description &&

{selected.description}

}
@@ -1376,8 +1325,8 @@ function AssurancePane({ )}
)} -
-
+ + -
+ ); } @@ -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({ diff --git a/webui/src/styles/risk-compliance.css b/webui/src/styles/risk-compliance.css index 6e3005e..cb30bc5 100644 --- a/webui/src/styles/risk-compliance.css +++ b/webui/src/styles/risk-compliance.css @@ -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); - } }