diff --git a/docs/MAIL_HANDBOOK.md b/docs/MAIL_HANDBOOK.md index beb595b..c3a6c60 100644 --- a/docs/MAIL_HANDBOOK.md +++ b/docs/MAIL_HANDBOOK.md @@ -253,6 +253,12 @@ split `mail:pop3:delete` when operators must never delete provider messages. Imported records follow configured Mail/records retention and require manual review for a data-subject request or deletion decision. +Contextual help is available from the page and from each policy-sensitive +source setting, credential field, size limit, import action, and destructive +confirmation. Press F1 while a control has focus to open the Mail-owned German +reference for the exact POP3 context; the same topic remains available through +the page help action. + ## Profile administration ### Roles diff --git a/package-lock.json b/package-lock.json index bd0b32b..c38fefb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@govoplan/mail-webui", - "version": "0.1.20", + "version": "0.1.21", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@govoplan/mail-webui", - "version": "0.1.20", + "version": "0.1.21", "peerDependencies": { "@govoplan/core-webui": "^0.1.18", "lucide-react": "^1.23.0", diff --git a/package.json b/package.json index e2a19f1..5e3a6ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/mail-webui", - "version": "0.1.20", + "version": "0.1.21", "private": true, "type": "module", "main": "webui/src/index.ts", diff --git a/pyproject.toml b/pyproject.toml index 490d0c3..7031047 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "govoplan-mail" -version = "0.1.20" +version = "0.1.21" description = "GovOPlaN mail module with backend and WebUI integration." readme = "README.md" requires-python = ">=3.12" diff --git a/src/govoplan_mail/backend/manifest.py b/src/govoplan_mail/backend/manifest.py index a8689c8..97b899f 100644 --- a/src/govoplan_mail/backend/manifest.py +++ b/src/govoplan_mail/backend/manifest.py @@ -400,7 +400,7 @@ POP3_PROVIDER = ExternalProviderDeclaration( manifest = ModuleManifest( id="mail", name="Mail", - version="0.1.20", + version="0.1.21", required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR), optional_dependencies=("campaigns", "addresses", "calendar", "postbox", "search"), provides_interfaces=( @@ -615,10 +615,75 @@ manifest = ModuleManifest( ), related_modules=("audit", "records"), unlocks=("Governed retirement of legacy POP3 mailboxes without making destructive retrieval the default.",), + translations={ + "de": { + "title": "Nachrichten aus einem alten POP3-Postfach importieren", + "summary": "Begrenzt viele POP3-Nachrichten in verschlüsselte lokale Prüfdatensätze übernehmen, ohne die Quelldaten standardmäßig zu löschen.", + "body": ( + "POP3 ist ausschließlich ein nachrangiger Migrationsweg und wird niemals stillschweigend aktiviert. " + "Eine Mail-Administration legt eine eigene POP3-Quelle an, aktiviert den Altimport ausdrücklich, " + "hinterlegt Zugangsdaten verschlüsselt, prüft TLS und Anmeldung und entscheidet getrennt, ob das Löschen " + "an der Quelle überhaupt angefordert werden darf. Importberechtigte laden eine begrenzte Live-Vorschau, " + "wählen Nachrichten anhand stabiler UIDL-Kennungen und übernehmen sie als verschlüsselte Datensätze mit " + "ausstehender Prüfung. Wiederholte Importe werden als Duplikate ausgewiesen und nicht erneut gespeichert. " + "Quelldaten bleiben unverändert, solange nicht die Endpunktrichtlinie Löschen nach Import erlaubt, die " + "ausführende Person zusätzlich mail:pop3:delete besitzt und das Löschen für den konkreten Stapel ausdrücklich " + "ausgewählt wird. Mail schreibt und protokolliert zuerst den lokalen Import und versucht erst danach die " + "Löschung beim Anbieter. Eine fehlgeschlagene oder unklare Löschung entfernt deshalb niemals den verwalteten " + "lokalen Datensatz und muss vor einem weiteren destruktiven Versuch abgestimmt werden. Für den laufenden " + "Postfachzugriff sind IMAP oder JMAP vorzuziehen." + ), + } + }, metadata={ "kind": "workflow", "route": "/mail/legacy-import", "screen": "Legacy POP3 import", + "help_contexts": [ + "mail.pop3", + "mail.pop3.source-editor", + "mail.pop3.action.reload", + "mail.pop3.action.create-source", + "mail.pop3.action.configure-source", + "mail.pop3.action.save-source", + "mail.pop3.action.test", + "mail.pop3.action.preview", + "mail.pop3.action.import", + "mail.pop3.field.profile", + "mail.pop3.field.name", + "mail.pop3.field.host", + "mail.pop3.field.port", + "mail.pop3.field.transport-security", + "mail.pop3.field.timeout", + "mail.pop3.field.max-message-size", + "mail.pop3.field.max-batch-size", + "mail.pop3.field.preview-body-lines", + "mail.pop3.field.username", + "mail.pop3.field.password", + "mail.pop3.field.enabled", + "mail.pop3.field.allow-delete-after-import", + "mail.pop3.field.message-selection", + "mail.pop3.field.delete-after-import", + "mail.pop3.confirm-delete-source", + ], + "fields": [ + { + "label": "Transport security", + "user_description": "Use TLS or STARTTLS; unencrypted transport remains subject to deployment egress policy and is not the safe default.", + }, + { + "label": "Message and batch limits", + "user_description": "Bound both each downloaded message and the total imported batch before provider content enters local encrypted storage.", + }, + { + "label": "Explicitly enable legacy import", + "user_description": "Keeps the source inert until endpoint metadata and its encrypted credential have both been stored.", + }, + { + "label": "Permit delete-after-import requests", + "user_description": "Allows, but never selects, the separately authorized destructive request; each batch still needs explicit confirmation.", + }, + ], "steps": [ "Ask a Mail administrator to configure and explicitly enable a dedicated POP3 legacy source.", "Test the source and refresh a bounded preview; no message flags or deletion state are changed.", @@ -630,6 +695,11 @@ manifest = ModuleManifest( "POP3 has no folder or flag semantics and is not the recommended protocol for ongoing mailbox access.", "Source deletion cannot be rolled back and may have an unknown outcome if the connection fails while QUIT commits deletions.", ], + "operational_consequences": [ + "Changing host, port, security, account identity, or import limits rotates the transport revision and invalidates stale previews.", + "Enabling source deletion creates an irreversible external effect boundary; unknown QUIT outcomes require reconciliation rather than blind retry.", + "Disabling legacy import leaves governed local imports and their evidence available for review while preventing new provider access.", + ], "verification": "Prove disabled policy, TLS and authentication diagnostics, non-destructive preview/import, UIDL duplicate prevention, encrypted raw retention, separate delete authorization, import-before-delete audit ordering, and failed or outcome-unknown deletion evidence.", }, ), @@ -656,6 +726,27 @@ manifest = ModuleManifest( DocumentationLink(label="Mail handbook", href="govoplan-mail/docs/MAIL_HANDBOOK.md", kind="repository"), ), related_modules=("access", "audit", "campaigns", "postbox"), + translations={ + "de": { + "title": "Mail-Daten in einer Auskunftsanfrage prüfen", + "summary": "Mandantenbezogene Mail-Metadaten zusammentragen und dabei Transportnachweise sowie die Zuständigkeit des externen Postfachs bewahren.", + "body": ( + "Der Mail-Provider für Betroffenenanfragen durchsucht den wirksamen Mandanten anhand normalisierter " + "Absender-, Empfänger- oder Unzustellbarkeitsadressen, direkter Mitgliedschaftsverweise und namensraumgebundener " + "Verweise auf Mail-Profile, Nachrichtenindizes, POP3-Importe, Zustellbefehle oder Rückläufer. Er grenzt nur " + "passende Absender-, An- und Cc-Beteiligte ab und liefert begrenzte Inhalte aus Nachrichtenindex oder Import, " + "sichere Metadaten persönlicher Profile sowie Zustell-, Versuch-, Abstimmungs- und Rückläufergebnisse. " + "Ausgeschlossen bleiben SMTP-, IMAP- und POP3-Konfiguration, Benutzernamen und Zugangsdaten, verschlüsselte " + "Nachrichten und Umschläge, POP3-UIDLs und Quellrevisionen, Ablehnungsdetails, Postfachordner und UIDs, Endpunkt- " + "und Zugangsdatenkennungen, Idempotenz- und Worker-Ansprüche, Diagnosen, Fehlertexte und undurchsichtige Nachweise. " + "Zustellversuche, Abstimmungsentscheidungen und Rückläuferbeobachtungen bleiben als unveränderliche Transport- " + "und Wiederherstellungsnachweise erhalten. Nachrichtenindizes, verschlüsselte POP3-Importe und persönliche Profile " + "müssen in Mail und im maßgeblichen externen Postfach manuell geprüft werden. Der Provider löscht nicht unmittelbar, " + "weil das Entfernen eines abgeleiteten oder importierten Datensatzes den Zustand seiner externen Quelle nicht belegt " + "und Änderungen an Profilen oder Zustellbefehlen Zugangsdaten, andere Personen und aufbewahrte Nachweise betreffen können." + ), + } + }, metadata={ "kind": "workflow", "route": "/admin?section=tenant-data-subject-requests", @@ -855,6 +946,19 @@ manifest = ModuleManifest( documentation_types=("admin", "user"), audience=("mail_user", "mail_admin", "administrator"), related_modules=("search",), + translations={ + "de": { + "title": "Berechtigte Postfachnachrichten durchsuchen", + "summary": "Begrenzte Metadaten und Vorschauen aus dem zwischengespeicherten Postfach für die berechtigungsbewusste Plattformsuche bereitstellen.", + "body": ( + "Ist Search installiert, stellt Mail seinen nur lesbaren Postfachzwischenspeicher bereit, niemals Zugangsdaten " + "oder unbegrenzte Rohnachrichten. Ergebnisse erfordern sowohl Leseberechtigung für das Postfach als auch " + "Nutzungsberechtigung für das Profil. Vor jeder Ausgabe werden aktueller Profilbereich, Richtlinie, Aktivität " + "sowie Sichtbarkeit für Mandant, Person, Gruppe oder Campaign erneut geprüft. Postfachaktualisierungen veröffentlichen " + "dauerhafte Indexänderungen; für die Abstimmung bleibt ein vollständiger Neuaufbau verfügbar." + ), + } + }, order=39, ), DocumentationTopic( @@ -883,6 +987,17 @@ manifest = ModuleManifest( related_modules=("campaigns",), unlocks=("Campaign-local delivery rules become visible when govoplan-campaign is installed.",), configuration_keys=("mail_profile_policy",), + translations={ + "de": { + "title": "Mail-Profile und Richtlinienhierarchie", + "summary": "Den Mail-Versand über wiederverwendbare SMTP-/IMAP-Profile und eine wirksame Richtlinie aus System-, Mandanten-, Eigentümer- und gegebenenfalls Campaign-Ebene steuern.", + "body": ( + "Die wirksame Richtlinie entscheidet, ob Personen ausschließlich freigegebene Profile auswählen dürfen " + "oder ob auf Personen-, Gruppen- und Campaign-Ebene zusätzliche wiederverwendbare Profile angelegt werden können. " + "Die Laufzeitdokumentation ergänzt die aktuelle Lage des Mandanten, wenn die handelnde Person die Mail-Profilrichtlinie lesen darf." + ), + } + }, metadata={ "kind": "reference", "route": "/settings?section=mail-profiles", @@ -918,6 +1033,21 @@ manifest = ModuleManifest( DocumentationLink(label="Mail handbook", href="govoplan-mail/docs/MAIL_HANDBOOK.md", kind="repository"), ), related_modules=("campaigns",), + translations={ + "de": { + "title": "Standardordner eines IMAP-Profils zuordnen", + "summary": "Zuordnungen für Posteingang, Gesendet, Entwürfe, Papierkorb, Archiv und Junk am wiederverwendbaren Mail-Profil speichern und durch begrenzte IMAP-Erkennung vorbelegen.", + "body": ( + "Ordnernamen sind Metadaten des Profils beziehungsweise Servers und keine Campaign-Einstellungen. Eine berechtigte " + "Profiladministration kann Namen direkt eintragen oder die Ordnererkennung verwenden; leere Zuordnungen behalten " + "das automatische Verhalten bei. Vorhandene Werte aus imap.sent_folder werden als Zuordnung für Gesendet angezeigt " + "und gespeichert, während das Kompatibilitätsfeld für ältere Verbraucher synchron bleibt. Eine Campaign-spezifische " + "Abweichung für den Gesendet-Ordner bleibt für diese Campaign maßgeblich und wird von der Profilerkennung nicht verändert. " + "Die Erkennung listet nur die beim Anbieter sichtbaren Namen auf und legt keine externen Ordner an, benennt sie nicht um, " + "verschiebt sie nicht und löscht sie nicht." + ), + } + }, metadata={ "kind": "workflow", "route": "/settings?section=mail-profiles", @@ -958,6 +1088,20 @@ manifest = ModuleManifest( ), ), related_modules=("campaigns", "calendar", "audit"), + translations={ + "de": { + "title": "Zustellstatus und Rückläufer verarbeiten", + "summary": "Einen berechtigten IMAP-Ordner beobachten und DSN-Ergebnisse mit dauerhaften Mail-Befehlen abgleichen.", + "body": ( + "Mail speichert die ausgehende RFC-Message-ID zusammen mit dem dauerhaften Befehl, verarbeitet begrenzte " + "message/delivery-status-Berichte ohne Postfachkennzeichen zu ändern und zeichnet je Empfänger idempotente " + "Beobachtungen auf. Die SMTP-Annahme bleibt von einem späteren Rückläufer getrennt. Nicht zuordenbare Berichte " + "bleiben zur Prüfung sichtbar; Mail speichert nur begrenzte Diagnosen und einen Digest des Rohinhalts, nicht den " + "Rohtext des Rückläufers. Ist Calendar aktiv, leitet derselbe begrenzte Quellscan iCalendar-Teile mit METHOD:REPLY " + "zur idempotenten Abstimmung der Teilnehmenden an Calendar weiter, ohne die Nachricht als Rückläufer einzuordnen." + ), + } + }, metadata={ "kind": "workflow", "route": "/mail/bounces", @@ -996,6 +1140,21 @@ manifest = ModuleManifest( ), related_modules=("campaigns", "access"), unlocks=("Reusable, governed delivery identities without cross-module secret duplication.",), + translations={ + "de": { + "title": "Mail besitzt Transportprofile und Zugangsdaten", + "summary": "Andere Module wählen berechtigte Mail-Profile über stabile Kennungen aus; sie kopieren weder SMTP-/IMAP-Einstellungen noch Geheimnisse.", + "body": ( + "Mail verschlüsselt Zugangsdaten, prüft Verbindungen, bewertet Profilbereich und Richtlinie und führt " + "revisionsgebundene Transportwirkungen aus, ohne die aufgelöste Konfiguration zurückzugeben. Zufällige gespeicherte " + "Revisionen wechseln, wenn sich normalisierte Transportdaten oder die Kontoidentität ändern; eine reine " + "Passwortrotation bleibt für bereits aufgebauten fachlichen Willen transparent. Campaign speichert nur " + "server.mail_profile_id und bereinigte Zustellnachweise. Campaign-lokale Transportfelder werden abgelehnt; " + "ältere Campaign-Datensätze bleiben gesperrt, bis eine ausdrückliche Profilmigration den ursprünglichen " + "Auditdatensatz bewahrt und eine bearbeitbare Version aktualisiert." + ), + } + }, metadata={ "kind": "reference", "route": "/settings?section=mail-profiles", @@ -1030,6 +1189,17 @@ manifest = ModuleManifest( ), related_modules=("campaigns",), unlocks=("Reusable SMTP/IMAP identities that consumers can reference without handling credentials.",), + translations={ + "de": { + "title": "Ein wiederverwendbares Mail-Profil auswählen und prüfen", + "summary": "Ein im aktuellen Kontext sichtbares Profil auswählen, seine SMTP- oder IMAP-Verbindung prüfen und im verbrauchenden Vorgang nur den stabilen Verweis speichern.", + "body": ( + "Profiltests prüfen die aktuelle Verbindung und Anmeldung mit den von Mail verwalteten Zugangsdaten. Sie belegen " + "weder eine spätere Richtlinienfreigabe in Campaign noch Zustellbarkeit, Empfängerannahme oder künftige Verfügbarkeit. " + "Verwenden Sie die Auswahlkomponente, statt Kennungen einzugeben oder Transporteinstellungen in ein anderes Modul zu kopieren." + ), + } + }, metadata={ "kind": "workflow", "route": "/settings?section=mail-profiles", @@ -1073,6 +1243,20 @@ manifest = ModuleManifest( DocumentationLink(label="Mail handbook", href="govoplan-mail/docs/MAIL_HANDBOOK.md", kind="repository"), ), unlocks=("Read-only access to an authorized operational mailbox without broadening profile administration rights.",), + translations={ + "de": { + "title": "Ein berechtigtes Postfach ohne Änderungen lesen", + "summary": "Ein IMAP-fähiges Profil auswählen, synchronisierte Ordner durchsuchen und begrenzte Nachrichteninhalte in der nur lesbaren Postfachoberfläche prüfen.", + "body": ( + "Der Postfachzugriff erfordert sowohl Leseberechtigung für das Postfach als auch Nutzungsberechtigung für ein " + "im Bereich der handelnden Person sichtbares Profil. Listen zeigen die vom Anbieter gelieferten Gelesen-/Ungelesen-Kennzeichen " + "und die Herkunft aus Livezugriff, Zwischenspeicher oder Aktualisierung, ohne diese Zustände zu verändern. HTML-Inhalte " + "werden isoliert und bereinigt; Anlagen, Inline-Verweise, nicht verfügbare Inhalte und Providerfehler bleiben ausdrücklich sichtbar. " + "Das Auflisten von Ordnern oder Nachrichten darf keine Nachricht als gelesen markieren, verschieben oder löschen und keine " + "unbegrenzten Inhalte offenlegen." + ), + } + }, metadata={ "kind": "workflow", "route": "/mail", @@ -1117,6 +1301,21 @@ manifest = ModuleManifest( ), related_modules=("audit",), unlocks=("A testable Mail trust boundary with fail-closed egress and auditable secret disposal.",), + translations={ + "de": { + "title": "Mail-Zugangsdaten, Netzausgänge und Außerbetriebnahme schützen", + "summary": "Geheimnisse in Mail belassen, jeden SMTP-/IMAP-/POP3-Gegenpunkt fest binden, Antworten begrenzen und eigene Zugangsdaten sofort mit nicht geheimem Auditnachweis löschen.", + "body": ( + "Der Zugriff von Konnektoren auf private Netze wird bereitstellungsweit gesteuert; dennoch wird jeder erlaubte " + "Hostname in einen freigegebenen Gegenpunkt aufgelöst und beim Aufbau des Sockets fest gebunden. Nicht unterstützte " + "Transporte scheitern vor dem Verbindungsaufbau. Beim Löschen eines Profils werden dessen verschlüsselte SMTP-, IMAP- " + "und POP3-Passwörter sofort entfernt und, sofern Geheimnisse vorhanden waren, mit nicht geheimen Angaben protokolliert. " + "Scheitert das Entfernen oder das Audit, wird die Aktion zurückgerollt. Verschlüsselte POP3-Importinhalte folgen der " + "Aufbewahrung von Mail beziehungsweise Records. Bei einer destruktiven Modulaußerbetriebnahme gilt dieselbe Regel: " + "Zuerst werden Geheimnisse entfernt, erst danach alle Mail-Tabellen einschließlich der Importdaten gelöscht." + ), + } + }, metadata={ "kind": "reference", "route": "/settings?section=mail-profiles", @@ -1152,6 +1351,27 @@ manifest = ModuleManifest( ), related_modules=("campaigns", "audit"), unlocks=("Profile-backed campaign delivery without cross-module credential or configuration copies.",), + translations={ + "de": { + "title": "Campaign über den Mail-Zustellvertrag integrieren", + "summary": "Campaign friert Profilverweis und undurchsichtige Revision ein; Mail berechtigt erneut, prüft die Revision, löst Zugangsdaten auf und führt die Wirkung in einem Aufruf aus.", + "body": ( + "Der Vertrag mail.campaign_delivery 0.2 gibt niemals entschlüsselte Zugangsdaten oder aufgelöste SMTP-/IMAP-Konfiguration zurück. " + "Mail vergleicht die erwartete zufällige Transportrevision vor dem Entschlüsseln protokollspezifischer Zugangsdaten und liefert " + "nur begrenzte, bereinigte Ergebnisse. Synchrone Stapel berechtigen die vollständige Empfängermenge und prüfen DNS, Netzausgang, " + "Verbindung, TLS und Anmeldung vor der ersten Wirkung. Wenn die Bereitstellungsrichtlinie es erlaubt, verwendet Mail die begrenzte " + "Verbindung erneut, prüft sie zuvor und verbindet vor der nächsten Nachricht neu, sobald sie veraltet ist. Ein Verbindungsverlust nach " + "Beginn von DATA bleibt mit unbekanntem Ergebnis bestehen und wird niemals automatisch wiederholt. Systemische Anmelde-, Absender- oder " + "Verbindungsfehler tragen stabile Ursachencodes, damit Campaign verbleibende Aufträge anhält und Verbindung, Neuverbindung, Fehler und " + "Pausenfortschritt anzeigt. Campaign besitzt gewöhnliche Empfängeraufträge; Berichtsnachrichten nutzen das verschlüsselte, idempotente " + "Zustellbefehls- und Versuchsledger von Mail. Jeder aktuelle SMTP- und Gesendet-Ordner-Versuch übergibt vor dem Providerkontakt eine " + "stabile Wirkungskennung an einen von Mail besessenen Core-Wiederherstellungsvorgang. Der Nachweis des Wirkungsbeginns verhindert blinde " + "Wiederzustellung; unbekannte Ergebnisse erfordern ausdrückliche Abstimmung, und rohe Empfängerablehnungen erfordern Mail-Diagnoseberechtigung. " + "Mail-Ausgangsverarbeitung und Aufbewahrungsläufe werden nach Mandantenberechtigung partitioniert. Das Deaktivieren von Mail lässt bereits " + "angenommene Befehle und Nachweise daher zur Klärung durch den Betrieb unverändert bestehen." + ), + } + }, metadata={ "kind": "reference", "route": "/campaigns/{campaign_id}/mail-settings", diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 8f316b8..77ca424 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -94,6 +94,34 @@ class MailManifestTests(unittest.TestCase): pop3_topic = topics["mail.workflow.legacy-pop3-import"] self.assertEqual(("user", "admin"), pop3_topic.documentation_types) self.assertIn("mail:pop3:import", pop3_topic.conditions[0].any_scopes) + self.assertTrue( + { + "mail.pop3", + "mail.pop3.source-editor", + "mail.pop3.action.reload", + "mail.pop3.action.save-source", + "mail.pop3.action.import", + "mail.pop3.field.transport-security", + "mail.pop3.field.max-message-size", + "mail.pop3.field.password", + "mail.pop3.field.delete-after-import", + "mail.pop3.confirm-delete-source", + }.issubset(pop3_topic.metadata["help_contexts"]) + ) + self.assertGreaterEqual(len(pop3_topic.metadata["fields"]), 4) + self.assertGreaterEqual( + len(pop3_topic.metadata["operational_consequences"]), + 3, + ) + + for topic in manifest.documentation: + with self.subTest(topic_id=topic.id): + german = topic.translations.get("de", {}) + for attribute in ("title", "summary", "body"): + self.assertTrue( + str(german.get(attribute, "")).strip(), + f"{topic.id} is missing its German {attribute}", + ) pop3_provider = next( item diff --git a/webui/package-lock.json b/webui/package-lock.json index 370dd77..912efb3 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -1,12 +1,12 @@ { "name": "@govoplan/mail-webui", - "version": "0.1.20", + "version": "0.1.21", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@govoplan/mail-webui", - "version": "0.1.20", + "version": "0.1.21", "devDependencies": { "typescript": "^5.7.2" }, diff --git a/webui/package.json b/webui/package.json index 7aa6ca1..2e95ba8 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/mail-webui", - "version": "0.1.20", + "version": "0.1.21", "private": true, "type": "module", "main": "src/index.ts", diff --git a/webui/scripts/test-interface-pattern-language.mjs b/webui/scripts/test-interface-pattern-language.mjs index 17f94bd..071dc4c 100644 --- a/webui/scripts/test-interface-pattern-language.mjs +++ b/webui/scripts/test-interface-pattern-language.mjs @@ -47,6 +47,25 @@ for (const sharedComponent of ["PageLayout", "PageActionBar", "SelectionList", " assert.match(legacyImport, /archetype="collection"/); assert.match(legacyImport, /variant="collection"[\s\S]*refreshable[\s\S]*reloadAction=/); assert.match(legacyImport, /topicId: "mail\.workflow\.legacy-pop3-import"/); +assert.match(legacyImport, /helpContextId="mail\.pop3"[\s\S]*helpTopicId="mail\.workflow\.legacy-pop3-import"/); +for (const helpContextId of [ + "mail.pop3.source-editor", + "mail.pop3.action.reload", + "mail.pop3.action.save-source", + "mail.pop3.action.import", + "mail.pop3.field.transport-security", + "mail.pop3.field.max-message-size", + "mail.pop3.field.max-batch-size", + "mail.pop3.field.password", + "mail.pop3.field.delete-after-import", + "mail.pop3.confirm-delete-source", +]) { + assert.match( + legacyImport, + new RegExp(`helpContextId(?:=|:\\s*)"${helpContextId.replaceAll(".", "\\.")}"`) + ); +} +assert.match(legacyImport, /data-help-context-id="mail\.pop3\.field\.message-selection"/); assert.match(legacyImport, / void runImport\(\)\}/); assert.match(legacyImport, /legacy_import_enabled: false[\s\S]*is_active: false[\s\S]*createMailServerCredential[\s\S]*updateMailServerEndpoint/); assert.match(legacyImport, /expected_transport_revision: preview\.transport_revision/); diff --git a/webui/src/features/mail/MailLegacyImportPage.tsx b/webui/src/features/mail/MailLegacyImportPage.tsx index 0d1c99e..2951281 100644 --- a/webui/src/features/mail/MailLegacyImportPage.tsx +++ b/webui/src/features/mail/MailLegacyImportPage.tsx @@ -338,6 +338,8 @@ export default function MailLegacyImportPage({ render: (item) => setSelectedUidls((current) => @@ -378,13 +380,16 @@ export default function MailLegacyImportPage({ archetype="collection" title="Legacy POP3 import" description="Migrate bounded messages into encrypted local review records. POP3 is disabled by default and is not recommended for ongoing mailbox access." + helpContextId="mail.pop3" + helpModuleId="mail" + helpTopicId="mail.workflow.legacy-pop3-import" actions={ void load(), disabled: Boolean(busy), disabledReason: busy ? "Wait for the current POP3 action to finish." : undefined }} + reloadAction={{ onReload: () => void load(), helpContextId: "mail.pop3.action.reload", helpModuleId: "mail", helpTopicId: "mail.workflow.legacy-pop3-import", disabled: Boolean(busy), disabledReason: busy ? "Wait for the current POP3 action to finish." : undefined }} contextActions={} helpAction={} - createAction={canManage ? : undefined} + createAction={canManage ? : undefined} />} loading={loading} loadingLabel="Loading legacy POP3 sources" @@ -419,6 +424,9 @@ export default function MailLegacyImportPage({ icon: