From 492ab7711aabf98ad7bfc6dcaee22d537da6a8d7 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 9 Sep 2026 02:03:55 +0200 Subject: [PATCH] fix(ui): centralize contextual help and navigation presentation 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. --- src/govoplan_notifications/backend/manifest.py | 18 ++++++++++++++++-- tests/test_interface_documentation_contract.py | 4 +++- .../test-notification-page-structure.mjs | 2 +- .../notifications/NotificationCenterPage.tsx | 14 ++++++++------ .../NotificationSettingsPanel.tsx | 9 +++------ .../NotificationSummaryWidget.tsx | 3 --- webui/src/module.ts | 2 ++ webui/src/styles/notifications.css | 8 +------- 8 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/govoplan_notifications/backend/manifest.py b/src/govoplan_notifications/backend/manifest.py index d99eec8..4a556a6 100644 --- a/src/govoplan_notifications/backend/manifest.py +++ b/src/govoplan_notifications/backend/manifest.py @@ -250,7 +250,10 @@ manifest = ModuleManifest( id="notifications.center-and-preferences", title="Use the notification center", summary="The title-bar badge and notification center collect durable notices that require attention outside an immediate request.", - body="Open the notification center to read, acknowledge, or follow notifications from enabled modules. The status dropdown uses the same checkbox filter as tables: select multiple states to include any of them, select all to remove the restriction, or deselect all to show no notifications. Status filtering happens before the latest 200 matching notifications are loaded; this list is not a complete archive. Changing the filter does not change delivery or read state. Reload refreshes the current selection without a cached response. Personal source-muting preferences remove matching entries from the personal list and badge counts even when a producer addressed the actor through an account, membership, or identity identifier; tenant-administrator evidence views remain complete. Preferences also control eligible delivery channels and categories. Disabling an optional external channel does not remove an unmuted in-product notification unless the originating module's retention policy does so.", + body="Documentation books sit immediately beside the visible heading or contextual label for " + "Notifications, notification preferences and delivery details, not among operational action " + "buttons. Field help remains beside its label. " + "Open the notification center to read, acknowledge, or follow notifications from enabled modules. The status dropdown uses the same checkbox filter as tables: select multiple states to include any of them, select all to remove the restriction, or deselect all to show no notifications. Status filtering happens before the latest 200 matching notifications are loaded; this list is not a complete archive. Changing the filter does not change delivery or read state. Reload refreshes the current selection without a cached response. Personal source-muting preferences remove matching entries from the personal list and badge counts even when a producer addressed the actor through an account, membership, or identity identifier; tenant-administrator evidence views remain complete. Preferences also control eligible delivery channels and categories. Disabling an optional external channel does not remove an unmuted in-product notification unless the originating module's retention policy does so.", documentation_types=("user",), audience=("user",), conditions=(DocumentationCondition(required_scopes=(READ_SCOPE,)),), @@ -280,6 +283,10 @@ manifest = ModuleManifest( "die außerhalb einer unmittelbaren Anfrage Aufmerksamkeit erfordern." ), "body": ( + "Dokumentationsbücher stehen unmittelbar neben der sichtbaren Überschrift oder " + "Kontextbezeichnung für Benachrichtigungen, Benachrichtigungseinstellungen und " + "Zustelldetails, nicht zwischen ausführbaren Aktionsschaltflächen. Feldhilfe bleibt neben der " + "Feldbezeichnung. " "Die Benachrichtigungszentrale zeigt Hinweise aktivierter Module zum Lesen, Bestätigen oder " "Weiterverfolgen. Der Statusfilter verwendet dieselben Kontrollkästchen wie Tabellen: mehrere " "Zustände einschließen, mit Alle auswählen die Einschränkung aufheben oder mit Alle abwählen " @@ -315,7 +322,10 @@ manifest = ModuleManifest( id="notifications.delivery-operations", title="Operate notification delivery", summary="Notifications persists message intent and bounded per-channel attempts before workers dispatch optional delivery channels.", - body="Producing modules emit notifications through the dispatch capability and do not own delivery credentials. In-product delivery is the baseline. Production email delivery is available only through an enabled Mail capability; file delivery remains development-only. Operators can inspect pending and failed attempts and retry only outcomes that are safe to repeat. Tenant module entitlement is checked before enqueue and again before worker delivery; disabling Notifications preserves accepted messages and exposes an operator action instead of silently consuming them. Notification lists batch-load delivery attempts for the already tenant- and recipient-filtered page, avoiding one additional database query per message. Attempt order and full evidence are preserved. Attempts whose tenant or notification reference does not match the parent are never projected, including already-loaded relationships; inconsistent stored evidence requires an authorized operator investigation rather than broader visibility. This is read optimization, not dispatch, and it does not truncate an individual notification's history.", + body="Documentation books sit immediately beside the visible heading or contextual label for " + "Notifications, notification preferences and delivery details, not among operational action " + "buttons. Field help remains beside its label. " + "Producing modules emit notifications through the dispatch capability and do not own delivery credentials. In-product delivery is the baseline. Production email delivery is available only through an enabled Mail capability; file delivery remains development-only. Operators can inspect pending and failed attempts and retry only outcomes that are safe to repeat. Tenant module entitlement is checked before enqueue and again before worker delivery; disabling Notifications preserves accepted messages and exposes an operator action instead of silently consuming them. Notification lists batch-load delivery attempts for the already tenant- and recipient-filtered page, avoiding one additional database query per message. Attempt order and full evidence are preserved. Attempts whose tenant or notification reference does not match the parent are never projected, including already-loaded relationships; inconsistent stored evidence requires an authorized operator investigation rather than broader visibility. This is read optimization, not dispatch, and it does not truncate an individual notification's history.", documentation_types=("admin",), audience=("tenant_admin", "operator", "module_admin"), related_modules=("mail", "audit", "ops"), @@ -338,6 +348,10 @@ manifest = ModuleManifest( "Notifications speichert Nachrichtenabsicht und begrenzte kanalbezogene Versuche, bevor Worker optionale Zustellkanäle ausführen." ), "body": ( + "Dokumentationsbücher stehen unmittelbar neben der sichtbaren Überschrift oder " + "Kontextbezeichnung für Benachrichtigungen, Benachrichtigungseinstellungen und " + "Zustelldetails, nicht zwischen ausführbaren Aktionsschaltflächen. Feldhilfe bleibt neben der " + "Feldbezeichnung. " "Erzeugende Module übergeben Benachrichtigungen über die Dispatch-Fähigkeit und verwalten keine " "Zugangsdaten für die Zustellung. Die interne Zustellung ist die Grundlage. Produktive " "E-Mail-Zustellung steht nur über eine aktivierte Mail-Fähigkeit bereit; Dateizustellung bleibt auf " diff --git a/tests/test_interface_documentation_contract.py b/tests/test_interface_documentation_contract.py index 791e17e..c1ddb6c 100644 --- a/tests/test_interface_documentation_contract.py +++ b/tests/test_interface_documentation_contract.py @@ -101,7 +101,9 @@ class NotificationsInterfaceDocumentationContractTests(unittest.TestCase): "useUnsavedDraftGuard", ): self.assertIn(component, settings) - self.assertIn("DocumentationHelpLink", widget) + contribution = (REPO_ROOT / "webui/src/module.ts").read_text(encoding="utf-8") + self.assertIn("documentation: NOTIFICATIONS_DOCUMENTATION", contribution) + self.assertNotIn("DocumentationHelpLink", widget) if __name__ == "__main__": diff --git a/webui/scripts/test-notification-page-structure.mjs b/webui/scripts/test-notification-page-structure.mjs index 24f0ea8..844eee5 100644 --- a/webui/scripts/test-notification-page-structure.mjs +++ b/webui/scripts/test-notification-page-structure.mjs @@ -8,7 +8,7 @@ const page = readFileSync(pagePath, "utf8"); const styles = readFileSync(stylesPath, "utf8"); assert.match(page, /SelectionList,[\s\S]*SelectionListItem,[\s\S]*from "@govoplan\/core-webui"/); -assert.match(page, //); +assert.match(page, /]*\bvariant="navigation")(?=[^>]*\blabel="i18n:govoplan-notifications\.notifications")(?=[^>]*\bclassName="notifications-selection-list")[^>]*>/); assert.match(page, / - + title={ + } + titleHelp={} primaryActions={