From 91f5a332a8905461ef0391d3741529f35eda8afc Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Sat, 22 Aug 2026 07:23:30 +0200 Subject: [PATCH] docs: complete public documentation baseline --- pyproject.toml | 2 +- src/govoplan_portal/backend/manifest.py | 51 +++++++++++++++++++++++- tests/test_service_directory.py | 21 ++++++++++ webui/package.json | 2 +- webui/scripts/test-interface-pattern.mjs | 3 +- webui/src/module.ts | 2 +- 6 files changed, 75 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index abab808..110cb65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "govoplan-portal" -version = "0.1.18" +version = "0.1.19" description = "GovOPlaN service discovery and public portal module." readme = "README.md" requires-python = ">=3.12" diff --git a/src/govoplan_portal/backend/manifest.py b/src/govoplan_portal/backend/manifest.py index 02aa69d..6714049 100644 --- a/src/govoplan_portal/backend/manifest.py +++ b/src/govoplan_portal/backend/manifest.py @@ -12,6 +12,7 @@ from govoplan_core.core.institutional import ( from govoplan_core.core.postbox import CAPABILITY_POSTBOX_PORTAL from govoplan_core.core.modules import ( CapabilityDocumentation, + DocumentationCondition, DocumentationLink, DocumentationTopic, FrontendModule, @@ -39,7 +40,7 @@ from govoplan_core.core.views import ViewSurface MODULE_ID = "portal" -MODULE_VERSION = "0.1.18" +MODULE_VERSION = "0.1.19" READ_SCOPE = "portal:service:read" SERVICE_LAUNCH_CAPABILITIES = tuple( service_launch_capability(kind) for kind in ("case", "form", "workflow") @@ -242,6 +243,18 @@ manifest = ModuleManifest( layer="available", documentation_types=("admin", "user"), audience=("user", "operator", "module_admin", "auditor"), + translations={ + "de": { + "title": "Datenschutzgrenze des Portals", + "summary": "Verstehen, warum das Portal keinen eigenen Datenschutzexport bereitstellt und welche maßgeblichen Module die projizierten Daten verwalten.", + "body": ( + "Das Portal speichert weder Dienstverzeichnis-, Dienststart-, Postfach-, Antragsstatus-, Antragsteller- noch Sitzungsdaten und stellt deshalb bewusst keinen doppelten Auskunftsanbieter bereit. " + "Services verwaltet die Zuordnung von Dienstdefinitionen; Cases, Forms Runtime und Workflow Engine verwalten die Wirkungen eines Starts; Postbox verwaltet Postfachdaten; Forms Runtime verwaltet Einreichungs- und Statuszugriffsdaten. " + "Core und der Betriebsverantwortliche verwalten Authentifizierungszustand, Anfrage- und Sicherheitsprotokolle sowie Infrastrukturtelemetrie. " + "Werden dem Portal dauerhafte Personalisierung, Analysen, gespeicherte Suchen, Kontaktdaten oder Sitzungen hinzugefügt, ist vor der Freigabe ein mandantenbezogener Datenschutzanbieter erforderlich." + ), + } + }, links=( DocumentationLink( label="Portal ownership boundary", @@ -304,6 +317,17 @@ manifest = ModuleManifest( layer="configured", documentation_types=("admin", "user"), audience=("public", "user", "operator", "module_admin"), + translations={ + "de": { + "title": "Antrag verfolgen", + "summary": "Den begrenzten öffentlichen Verlauf über das für die genaue veröffentlichte Formularrevision konfigurierte Zugriffsprofil einsehen.", + "body": ( + "Das Portal zeigt die von Forms Runtime verwaltete Antragsstatusprojektion. Für jede genaue veröffentlichte Formularrevision wählt die Dienstadministration zwischen ausschließlich authentifiziertem Zugriff, einem kurzlebigen E-Mail-Link und einem dauerhaften öffentlichen Inhaberlink. " + "Beim authentifizierten Zugriff wird das Antragstellerkonto geprüft. Anforderungen eines E-Mail-Links liefern für passende und unpassende Angaben dieselbe Antwort, widerrufen beim erneuten Versand den vorherigen Link und setzen konfigurierte Notifications- und Mail-Zustellung voraus. Ein dauerhafter Link läuft nicht ab und erfordert keine Anmeldung; er ist deshalb wie ein Inhabergeheimnis zu behandeln. " + "Die Seite zeigt ausschließlich Lebenszyklusstatus, Aktualisierungszeiten, eine Vorgangskennung und die Einreichungsbestätigung. Formularwerte, Nachweise, interne Notizen, handelnde Personen, Entscheidungsbegründungen und Details von Modulübergaben bleiben verborgen." + ), + } + }, links=( DocumentationLink( label="Applicant status", @@ -336,6 +360,22 @@ manifest = ModuleManifest( layer="available", documentation_types=("admin", "user"), audience=("user", "operator", "module_admin"), + conditions=( + DocumentationCondition( + required_modules=("portal",), + required_scopes=(READ_SCOPE,), + ), + ), + translations={ + "de": { + "title": "Dienstverzeichnis", + "summary": "Verfügbare Dienste der konfigurierten Institution finden und ihre maßgeblichen Verfügbarkeitsgrenzen verstehen.", + "body": ( + "Das Portal zeigt versionierte Dienstdefinitionen ihrer jeweils verantwortlichen Anbieter. Veröffentlichte Dienste können verfügbar sein, mit einer Begründung als nicht verfügbar erscheinen oder unauffindbar bleiben, wenn sie für die aktuelle Zielgruppe nicht gelten. " + "Beim Öffnen wird die genaue Revision erneut geprüft und der Start eines Falls, Formulars oder Workflows an die installierte Besitzerfunktion übergeben." + ), + } + }, links=( DocumentationLink( label="Service directory architecture", @@ -348,6 +388,15 @@ manifest = ModuleManifest( kind="repository", ), ), + metadata={ + "kind": "workflow", + "help_contexts": ["portal.service-directory"], + "steps": [ + "Open the service directory and select an applicable published service.", + "Review any explained availability restriction before continuing.", + "Open the service so Portal rechecks the exact revision and hands the start to its owning module.", + ], + }, ), ), architecture=ModuleArchitectureDeclaration( diff --git a/tests/test_service_directory.py b/tests/test_service_directory.py index 7907387..9487940 100644 --- a/tests/test_service_directory.py +++ b/tests/test_service_directory.py @@ -457,6 +457,27 @@ class PortalServiceDirectoryTests(unittest.TestCase): ) self.assertTrue({"admin", "user"}.issubset(dsar_topic.documentation_types)) + topics = {topic.id: topic for topic in manifest.documentation} + self.assertTrue( + all( + all( + topic.translations.get("de", {}).get(field) + for field in ("title", "summary", "body") + ) + for topic in topics.values() + ) + ) + workflow = topics["portal.service-directory"] + self.assertEqual("workflow", workflow.metadata["kind"]) + self.assertTrue(workflow.conditions) + self.assertTrue( + all( + condition.required_scopes or condition.any_scopes + for condition in workflow.conditions + ) + ) + self.assertEqual("reference", dsar_topic.metadata["kind"]) + if __name__ == "__main__": unittest.main() diff --git a/webui/package.json b/webui/package.json index 3d3cb43..e732e67 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/portal-webui", - "version": "0.1.18", + "version": "0.1.19", "private": true, "type": "module", "main": "src/index.ts", diff --git a/webui/scripts/test-interface-pattern.mjs b/webui/scripts/test-interface-pattern.mjs index 79c9c00..d3a74f3 100644 --- a/webui/scripts/test-interface-pattern.mjs +++ b/webui/scripts/test-interface-pattern.mjs @@ -2,7 +2,6 @@ import assert from "node:assert/strict"; import fs from "node:fs"; const page = fs.readFileSync("src/features/portal/PortalPage.tsx", "utf8"); -const styles = fs.readFileSync("src/styles/portal.css", "utf8"); assert.ok(page.includes("DocumentationHelpLink"), "Portal exposes configured-system help"); assert.ok(page.includes("ActionBlockerHint"), "Unavailable launches expose the shared structured blocker"); @@ -12,6 +11,6 @@ assert.ok(page.includes('aria-live="polite"'), "Changing result counts are annou assert.ok(page.includes("useGuardedNavigate"), "Internal launch handoffs respect unsaved-work navigation"); assert.ok(!page.includes("window.alert("), "Portal must not use browser alerts"); assert.ok(!/<(div|span|li|tr)\b[^>]*\bonClick\s*=/.test(page), "Portal uses semantic interactive elements"); -assert.ok(styles.includes("@media (max-width: 720px)"), "Portal retains a narrow-viewport toolbar layout"); +assert.ok(page.includes(" import("./features/portal/PortalStatusPage") export const portalModule: PlatformWebModule = { id: "portal", label: "Services", - version: "0.1.8", + version: "0.1.19", optionalDependencies: ["access", "services", "cases", "forms", "forms_runtime", "workflow_engine"], routes: [ {