From 4f02425a28caaed93130c25a597e5a7f15225569 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Sun, 23 Aug 2026 20:19:13 +0200 Subject: [PATCH] docs(calendar): complete German reference coverage --- package.json | 2 +- pyproject.toml | 2 +- src/govoplan_calendar/__init__.py | 2 +- src/govoplan_calendar/backend/manifest.py | 324 ++++++++++++++++-- .../test_interface_documentation_contract.py | 55 ++- webui/package.json | 2 +- 6 files changed, 336 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index 7c44f38..72f4218 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/calendar-webui", - "version": "0.1.18", + "version": "0.1.20", "private": true, "type": "module", "main": "webui/src/index.ts", diff --git a/pyproject.toml b/pyproject.toml index 10e4171..89e8dd6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "govoplan-calendar" -version = "0.1.18" +version = "0.1.20" description = "GovOPlaN calendar module with VEVENT storage and WebUI integration." readme = "README.md" requires-python = ">=3.12" diff --git a/src/govoplan_calendar/__init__.py b/src/govoplan_calendar/__init__.py index 224b9ab..d6aaa8d 100644 --- a/src/govoplan_calendar/__init__.py +++ b/src/govoplan_calendar/__init__.py @@ -2,4 +2,4 @@ __all__ = ["__version__"] -__version__ = "0.1.18" +__version__ = "0.1.20" diff --git a/src/govoplan_calendar/backend/manifest.py b/src/govoplan_calendar/backend/manifest.py index 2c6c965..d8c7250 100644 --- a/src/govoplan_calendar/backend/manifest.py +++ b/src/govoplan_calendar/backend/manifest.py @@ -6,7 +6,10 @@ from pathlib import Path from sqlalchemy import inspect from govoplan_calendar.backend.db import models as calendar_models # noqa: F401 - populate Calendar ORM metadata -from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER +from govoplan_core.core.access import ( + CAPABILITY_AUTH_PERMISSION_EVALUATOR, + CAPABILITY_AUTH_PRINCIPAL_RESOLVER, +) from govoplan_core.core.calendar import ( CALENDAR_AVAILABILITY_READ_SCOPE, CALENDAR_EVENT_WRITE_SCOPE, @@ -15,7 +18,10 @@ from govoplan_core.core.calendar import ( CAPABILITY_CALENDAR_OUTBOX, CAPABILITY_CALENDAR_SCHEDULING, ) -from govoplan_core.core.module_guards import drop_table_retirement_provider, persistent_table_uninstall_guard +from govoplan_core.core.module_guards import ( + drop_table_retirement_provider, + persistent_table_uninstall_guard, +) from govoplan_core.core.modules import ( CapabilityDocumentation, DocumentationCondition, @@ -110,6 +116,7 @@ CALENDAR_ARCHITECTURE = ModuleArchitectureDeclaration( ), ) + def _read_only_calendar_provider( *, provider_id: str, @@ -249,13 +256,27 @@ CALENDAR_EXTERNAL_PROVIDERS = ( ProviderObjectDeclaration( object_type="calendar_collection", field_groups=("identity", "display", "sync_state", "resource_mapping"), - authority_modes=("external_authoritative", "external_mirror", "governed_sync"), + authority_modes=( + "external_authoritative", + "external_mirror", + "governed_sync", + ), default_authority_mode="governed_sync", ), ProviderObjectDeclaration( object_type="calendar_event", - field_groups=("identity", "schedule", "recurrence", "participants", "content"), - authority_modes=("external_authoritative", "external_mirror", "governed_sync"), + field_groups=( + "identity", + "schedule", + "recurrence", + "participants", + "content", + ), + authority_modes=( + "external_authoritative", + "external_mirror", + "governed_sync", + ), default_authority_mode="governed_sync", ), ), @@ -272,19 +293,37 @@ CALENDAR_EXTERNAL_PROVIDERS = ( outcome_unknown="A timed-out remote write is outcome-unknown and is reconciled before retry.", outcome_unknown_supported=True, evidence="The Open-Xchange profile reference, mapping references, operation intent, ETag, and reconciliation result are retained.", - audit_event_types=("calendar.sync.requested", "calendar.sync.completed", "calendar.sync.conflict", "calendar.sync.reconciled"), + audit_event_types=( + "calendar.sync.requested", + "calendar.sync.completed", + "calendar.sync.conflict", + "calendar.sync.reconciled", + ), correction="A later conditional update or tombstone corrects external state after reconciliation.", rollback="Remote effects are not treated as transactionally rollback-safe.", compensation="A compensating event update or delete may be queued after remote state is known.", reconciliation="Read by CalDAV href and VEVENT UID, compare ETag and content, then classify the result.", outage="The local projection remains available with stale markers and durable pending writes.", classifications=("internal", "confidential", "restricted"), - purposes=("calendar collaboration", "availability", "resource booking", "meeting coordination"), + purposes=( + "calendar collaboration", + "availability", + "resource booking", + "meeting coordination", + ), retention="Calendar event, outbox, audit, profile-binding, and credential retention remain independent policies.", secret_handling="Calendar stores only scoped credential references or encrypted Calendar-owned credentials.", ), - capability_names=(CAPABILITY_CALENDAR_EXTERNAL_PROFILES, CAPABILITY_CALENDAR_OUTBOX, CAPABILITY_CALENDAR_SCHEDULING), - interface_names=("calendar.external_profiles", "calendar.outbox", "calendar.scheduling"), + capability_names=( + CAPABILITY_CALENDAR_EXTERNAL_PROFILES, + CAPABILITY_CALENDAR_OUTBOX, + CAPABILITY_CALENDAR_SCHEDULING, + ), + interface_names=( + "calendar.external_profiles", + "calendar.outbox", + "calendar.scheduling", + ), documentation_topic_ids=("calendar.external-sources-and-sync",), ), _read_only_calendar_provider( @@ -325,10 +364,18 @@ def _calendar_retirement_provider(session: object | None, module_id: str): return plan def executor(execute_session: object, execute_module_id: str) -> None: - if not hasattr(execute_session, "get_bind") or not hasattr(execute_session, "query"): - raise RuntimeError("No database session is available for Calendar credential retirement.") - if inspect(execute_session.get_bind()).has_table(calendar_models.CalendarSyncCredential.__tablename__): - from govoplan_calendar.backend.service import delete_calendar_credentials_for_retirement + if not hasattr(execute_session, "get_bind") or not hasattr( + execute_session, "query" + ): + raise RuntimeError( + "No database session is available for Calendar credential retirement." + ) + if inspect(execute_session.get_bind()).has_table( + calendar_models.CalendarSyncCredential.__tablename__ + ): + from govoplan_calendar.backend.service import ( + delete_calendar_credentials_for_retirement, + ) delete_calendar_credentials_for_retirement(execute_session) base_executor(execute_session, execute_module_id) @@ -358,15 +405,51 @@ def _permission(scope: str, label: str, description: str) -> PermissionDefinitio PERMISSIONS = ( - _permission("calendar:calendar:read", "View calendars", "List tenant calendar collections and metadata."), - _permission("calendar:calendar:write", "Manage calendars", "Create and edit tenant calendar collections."), - _permission("calendar:calendar:admin", "Administer calendars", "Delete calendars and manage tenant-level calendar settings."), - _permission("calendar:event:read", "View calendar events", "List and inspect calendar events."), - _permission(CALENDAR_EVENT_WRITE_SCOPE, "Manage calendar events", "Create and edit calendar events."), - _permission("calendar:event:delete", "Delete calendar events", "Delete or cancel calendar events where policy allows it."), - _permission("calendar:event:import", "Import iCalendar events", "Import VEVENT data from iCalendar sources."), - _permission("calendar:event:export", "Export iCalendar events", "Export events as text/calendar VEVENT data."), - _permission(CALENDAR_AVAILABILITY_READ_SCOPE, "Read availability", "Read free/busy and availability data for integrations."), + _permission( + "calendar:calendar:read", + "View calendars", + "List tenant calendar collections and metadata.", + ), + _permission( + "calendar:calendar:write", + "Manage calendars", + "Create and edit tenant calendar collections.", + ), + _permission( + "calendar:calendar:admin", + "Administer calendars", + "Delete calendars and manage tenant-level calendar settings.", + ), + _permission( + "calendar:event:read", + "View calendar events", + "List and inspect calendar events.", + ), + _permission( + CALENDAR_EVENT_WRITE_SCOPE, + "Manage calendar events", + "Create and edit calendar events.", + ), + _permission( + "calendar:event:delete", + "Delete calendar events", + "Delete or cancel calendar events where policy allows it.", + ), + _permission( + "calendar:event:import", + "Import iCalendar events", + "Import VEVENT data from iCalendar sources.", + ), + _permission( + "calendar:event:export", + "Export iCalendar events", + "Export events as text/calendar VEVENT data.", + ), + _permission( + CALENDAR_AVAILABILITY_READ_SCOPE, + "Read availability", + "Read free/busy and availability data for integrations.", + ), ) ROLE_TEMPLATES = ( @@ -410,11 +493,32 @@ def _tenant_summary(session, tenant_id: str) -> dict[str, int]: ) return { - "calendars": session.query(CalendarCollection).filter(CalendarCollection.tenant_id == tenant_id, CalendarCollection.deleted_at.is_(None)).count(), - "calendar_events": session.query(CalendarEvent).filter(CalendarEvent.tenant_id == tenant_id, CalendarEvent.deleted_at.is_(None)).count(), - "calendar_sync_sources": session.query(CalendarSyncSource).filter(CalendarSyncSource.tenant_id == tenant_id, CalendarSyncSource.deleted_at.is_(None)).count(), - "calendar_sync_credentials": session.query(CalendarSyncCredential).filter(CalendarSyncCredential.tenant_id == tenant_id, CalendarSyncCredential.deleted_at.is_(None)).count(), - "calendar_view_preferences": session.query(CalendarViewPreference).filter(CalendarViewPreference.tenant_id == tenant_id).count(), + "calendars": session.query(CalendarCollection) + .filter( + CalendarCollection.tenant_id == tenant_id, + CalendarCollection.deleted_at.is_(None), + ) + .count(), + "calendar_events": session.query(CalendarEvent) + .filter( + CalendarEvent.tenant_id == tenant_id, CalendarEvent.deleted_at.is_(None) + ) + .count(), + "calendar_sync_sources": session.query(CalendarSyncSource) + .filter( + CalendarSyncSource.tenant_id == tenant_id, + CalendarSyncSource.deleted_at.is_(None), + ) + .count(), + "calendar_sync_credentials": session.query(CalendarSyncCredential) + .filter( + CalendarSyncCredential.tenant_id == tenant_id, + CalendarSyncCredential.deleted_at.is_(None), + ) + .count(), + "calendar_view_preferences": session.query(CalendarViewPreference) + .filter(CalendarViewPreference.tenant_id == tenant_id) + .count(), "calendar_outbox_pending": session.query(CalendarOutboxOperation) .filter( CalendarOutboxOperation.tenant_id == tenant_id, @@ -464,7 +568,9 @@ def _calendar_outbox_provider(context: ModuleContext) -> object: def _calendar_external_profile_provider(context: ModuleContext) -> object: del context - from govoplan_calendar.backend.capabilities import SqlCalendarExternalProfileProvider + from govoplan_calendar.backend.capabilities import ( + SqlCalendarExternalProfileProvider, + ) return SqlCalendarExternalProfileProvider() @@ -509,9 +615,22 @@ def _open_xchange_provider_states(context): manifest = ModuleManifest( id="calendar", name="Calendar", - version="0.1.19", - required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR), - optional_dependencies=("mail", "tasks", "scheduling", "appointments", "workflow_engine", "notifications", "dms", "connectors", "search"), + version="0.1.20", + required_capabilities=( + CAPABILITY_AUTH_PRINCIPAL_RESOLVER, + CAPABILITY_AUTH_PERMISSION_EVALUATOR, + ), + optional_dependencies=( + "mail", + "tasks", + "scheduling", + "appointments", + "workflow_engine", + "notifications", + "dms", + "connectors", + "search", + ), provides_interfaces=( ModuleInterfaceProvider(name="calendar.outbox", version="0.1.8"), ModuleInterfaceProvider(name="calendar.scheduling", version="0.1.9"), @@ -578,7 +697,12 @@ manifest = ModuleManifest( ), layer="configured", documentation_types=("admin",), - audience=("privacy_officer", "calendar_manager", "records_manager", "operator"), + audience=( + "privacy_officer", + "calendar_manager", + "records_manager", + "operator", + ), order=17, conditions=( DocumentationCondition( @@ -603,6 +727,29 @@ manifest = ModuleManifest( ), ), related_modules=("access", "audit", "ops", "scheduling"), + translations={ + "de": { + "title": "Calendar-Daten in einer Betroffenenanfrage prüfen", + "summary": ( + "Mandantenbezogene Ereignis-, Teilnahme-, Präferenz-, Synchronisations- und Migrationsmetadaten erfassen, ohne " + "Connector-Geheimnisse offenzulegen." + ), + "body": ( + "Der DSAR-Provider von Calendar durchsucht den wirksamen Mandanten anhand normalisierter E-Mail-Adressen von " + "Organisierenden oder Teilnehmenden, direkter Mitgliedschaftsverweise und namensraumgebundener Calendar-Sammlungs- oder " + "Ereignisverweise. Er liefert nur den passenden Organisierenden- oder Teilnehmendenausschnitt zusammen mit berechtigtem " + "Ereignis- und Sammlungskontext, persönlichen Ansichtspräferenzen, eigener Synchronisationskonfiguration, dauerhaften " + "Outbox-Ergebnissen und Migrationsnachweisen. Ausgeschlossen sind rohe ICS- und vollständige iCalendar-Daten, " + "Sammlungs-URLs, entfernte hrefs und ETags, Synchronisationstoken, Zugangsdaten, verschlüsselte Geheimnisse, Worker-Leases, " + "Idempotenzmaterial, Provider-Fehlertexte sowie unbeteiligte Personen oder Ereignisse. Synchronisierte, korrelierte, " + "gelöschte, eingereihte und migrierte Zustände bleiben mit Begründung erhalten, weil sie institutionelle Liefer-, Abgleich- " + "oder Wiederherstellungsnachweise sind. Lokale Ereignis-, Sammlungs- und aktive Zugangsdatenmetadaten erfordern eine " + "koordinierte manuelle Prüfung. Nach erneuter Prüfung von Mandant und Eigentum darf der Provider die persönliche " + "Calendar-Ansichtspräferenz idempotent löschen; Ereignisse, Teilnehmerlisten, synchronisierte Ressourcen, Zugangsdaten " + "oder Migrationsdatensätze verändert er niemals direkt." + ), + } + }, metadata={ "kind": "workflow", "route": "/admin?section=tenant-data-subject-requests", @@ -653,7 +800,10 @@ manifest = ModuleManifest( ), } }, - metadata={"kind": "reference", "help_contexts": ["calendar.quick_access.agenda"]}, + metadata={ + "kind": "reference", + "help_contexts": ["calendar.quick_access.agenda"], + }, order=18, ), DocumentationTopic( @@ -670,6 +820,20 @@ manifest = ModuleManifest( documentation_types=("admin", "user"), audience=("user", "calendar_manager", "administrator"), related_modules=("search",), + translations={ + "de": { + "title": "Berechtigte Kalenderereignisse durchsuchen", + "summary": ( + "Ereignistitel, Zeitpläne, Orte und Beschreibungen für die berechtigungsbewusste Plattform-Suche bereitstellen." + ), + "body": ( + "Ist Search installiert, liefert Calendar nicht gelöschte Ereignisse aus sichtbaren Kalendern. Jedes Ergebnis bleibt " + "mandantengebunden und prüft vor der Ausgabe erneut die aktuelle Leseberechtigung für Ereignisse sowie bei privaten " + "Kalendern Eigentum oder Gruppenmitgliedschaft. Festgeschriebene Ereignisänderungen aktualisieren den abgeleiteten Index " + "über die Plattform-Event-Outbox; ein Neuaufbau von Search verändert niemals Calendar-Daten." + ), + } + }, order=19, ), DocumentationTopic( @@ -680,6 +844,24 @@ manifest = ModuleManifest( documentation_types=("user",), audience=("user", "calendar_manager"), related_modules=("scheduling", "notifications"), + translations={ + "de": { + "title": "Kalender und Ereignisse verwenden", + "summary": ( + "Kalendersammlungen anlegen und ganztägige oder zeitgebundene Ereignisse in fortlaufender, Monats-, Wochen-, " + "Arbeitswochen- und Tagesansicht bearbeiten." + ), + "body": ( + "Calendar merkt sich gewählte Ansicht und Präferenzen. Ereignisse können mit der entsprechenden Berechtigung angelegt, " + "bearbeitet, verschoben, in ihrer Dauer geändert, wiederholt, importiert, exportiert oder gelöscht werden. Ganztägige " + "Ereignisse verwenden Datumswerte statt lokaler Uhrzeiten; zeitgebundene Ereignisse bewahren zeitzonenbezogene Start- und " + "Endwerte. Scheduling darf eine ausgewählte vorläufige Vormerkung direkt bestätigen und nicht verwendete Vormerkungen " + "über die neutrale Calendar-Fähigkeit idempotent freigeben. Calendar besitzt den entstehenden lokalen Löschmarker, die " + "synchronisierte Outbox-Operation sowie Wiederholungs- und Abgleichsnachweise; ein bereits freigegebenes oder fehlendes " + "Ereignis gilt bei Wiederholung als akzeptiert und nicht als doppelter Fehler." + ), + } + }, metadata={ "kind": "reference", "help_contexts": [ @@ -706,6 +888,29 @@ manifest = ModuleManifest( documentation_types=("admin", "user"), audience=("user", "calendar_manager", "operator"), related_modules=("connectors", "audit", "ops"), + translations={ + "de": { + "title": "Externe Kalender verbinden und synchronisieren", + "summary": ( + "Calendar unterstützt lokale Sammlungen, bidirektionale CalDAV- und Open-Xchange-Profile sowie schreibgeschützte " + "ICS-/webcal-, Microsoft-Graph- und Exchange-Web-Services-Quellen." + ), + "body": ( + "Jede externe Quelle hält URL, Synchronisationsrichtung, Status und Zugangsdatenverweis gemeinsam mit der " + "Calendar-Sammlung. Open-Xchange verwendet den bewährten CalDAV-Transport und bewahrt dabei Connector-Profil-, " + "Identitäts-/Gruppenzuordnungs- und Ressourcenkalenderverweise. Manuelle und geplante Synchronisation zeichnet begrenzte " + "Ergebnisse auf. Geplante Quellen- und Outbox-Worker teilen Arbeit nach Mandantenberechtigung; wird Calendar deaktiviert, " + "bleiben angenommene Vorgänge erhalten und verlangen Betriebshandeln, statt einen entfernten Provider anzusprechen. " + "CalDAV-Schreibvorgänge verwenden bedingte Anfragen und dauerhaften Outbox-Zustand. Konflikte und unbekannte Ergebnisse " + "erfordern Synchronisation oder ausdrücklichen Abgleich statt blinder Wiederholung. Das Verschieben zwischen zwei " + "bidirektionalen CalDAV-Kalendern ist ein administrativ genehmigter Migrationsstapel: Alle Zielressourcen müssen kopiert " + "sein, bevor eine Quellressource bedingt mit ihrem aufgezeichneten ETag gelöscht wird. Änderungen an Kalender und " + "Ereignissen bleiben gesperrt, während Fortschritt, Konflikte, Abbruchmöglichkeit und Nachweise sichtbar sind. Das " + "Entfernen einer externen Quelle entfernt die Verbindung; das Löschen eines lokalen Kalenders löscht dessen Ereignisse " + "nach Bestätigung oder Übertragung." + ), + } + }, metadata={ "kind": "reference", "help_contexts": [ @@ -742,6 +947,23 @@ manifest = ModuleManifest( ), ), related_modules=("campaigns", "mail", "audit"), + translations={ + "de": { + "title": "Campaign-Einladungen und Antworten nachverfolgen", + "summary": ( + "Akzeptierte Campaign-Einladungslieferungen als korrelierte VEVENTs spiegeln und den Teilnahmestatus in Calendar " + "maßgeblich halten." + ), + "body": ( + "Campaign kann vor der Zustellung einen METHOD:REQUEST-Anhang erzeugen und das korrelierte Calendar-Ereignis erst nach " + "Annahme der Lieferung anlegen oder aktualisieren. Calendar speichert Korrelation, PARTSTAT der Teilnehmenden, " + "Antwortzeit, begrenzte Nachweise, Synchronisationszustand und etwaiges eingeschränktes Verhalten. Beobachtet Mail einen " + "berechtigten IMAP-Ordner, werden METHOD:REPLY-Teile idempotent an Calendar weitergeleitet. Campaign-Berichte fragen den " + "aktuellen Zustand gesammelt ab, statt ihn in Campaign-Datensätze zu kopieren. Wiederkehrende Campaign-Einladungsserien " + "bleiben ein eigener Ablauf." + ), + } + }, metadata={"kind": "workflow"}, ), DocumentationTopic( @@ -759,6 +981,22 @@ manifest = ModuleManifest( documentation_types=("admin", "user"), audience=("calendar_manager", "operator", "tenant_admin"), related_modules=("ops", "audit"), + translations={ + "de": { + "title": "Synchronisierte Kalenderschreibvorgänge wiederherstellen", + "summary": ( + "Ungelöste CalDAV-Schreibvorgänge prüfen und ausschließlich die neueste sichere Ressourcengeneration wiederherstellen." + ), + "body": ( + "Calendar-Administrierende öffnen Ausgehende Änderungen in den Einstellungen eines synchronisierten Kalenders. Die " + "begrenzte Historie erläutert Versuche, Konflikte, endgültig fehlgeschlagene Arbeit, veraltete Generationen, deaktivierte " + "Quellen und aktive Worker-Leases. Wiederholen reiht einen fehlgeschlagenen Sollzustand erneut ein, Abgleichen vergleicht " + "ihn mit der entfernten Ressource und Verwerfen gibt den lokalen Sollzustand nach einer getrennten Warnung auf, sodass die " + "nächste vollständige Synchronisation den entfernten Zustand übernehmen kann. Automatische Auslieferung und fällige " + "Quellenausführung bleiben reine Worker-Operationen für Dienstkonten und werden nicht als interaktive Steuerungen angeboten." + ), + } + }, metadata={ "kind": "runbook", "help_contexts": [ @@ -787,7 +1025,15 @@ manifest = ModuleManifest( audience=("privacy_officer", "calendar_manager", "records_manager"), ), }, - nav_items=(NavItem(path="/calendar", label="Calendar", icon="calendar", required_any=("calendar:event:read",), order=55),), + nav_items=( + NavItem( + path="/calendar", + label="Calendar", + icon="calendar", + required_any=("calendar:event:read",), + order=55, + ), + ), frontend=FrontendModule( module_id="calendar", package_name="@govoplan/calendar-webui", @@ -799,7 +1045,15 @@ manifest = ModuleManifest( order=55, ), ), - nav_items=(NavItem(path="/calendar", label="Calendar", icon="calendar", required_any=("calendar:event:read",), order=55),), + nav_items=( + NavItem( + path="/calendar", + label="Calendar", + icon="calendar", + required_any=("calendar:event:read",), + order=55, + ), + ), view_surfaces=( ViewSurface( id="calendar.navigation", diff --git a/tests/test_interface_documentation_contract.py b/tests/test_interface_documentation_contract.py index 6b19483..e6796b1 100644 --- a/tests/test_interface_documentation_contract.py +++ b/tests/test_interface_documentation_contract.py @@ -10,6 +10,14 @@ REPO_ROOT = Path(__file__).resolve().parents[1] class CalendarInterfaceDocumentationContractTests(unittest.TestCase): + def test_all_static_topics_have_complete_german_content(self) -> None: + for topic in get_manifest().documentation: + german = (topic.translations or {}).get("de", {}) + self.assertEqual({"title", "summary", "body"}, set(german), topic.id) + self.assertTrue( + all(str(value).strip() for value in german.values()), topic.id + ) + def test_backend_surfaces_and_hierarchy_remain_declared(self) -> None: frontend = get_manifest().frontend self.assertIsNotNone(frontend) @@ -31,13 +39,21 @@ class CalendarInterfaceDocumentationContractTests(unittest.TestCase): } self.assertEqual(expected, set(surfaces)) self.assertEqual("calendar.page", surfaces["calendar.page.sidebar"].parent_id) - self.assertEqual("calendar.page.sidebar", surfaces["calendar.page.agenda"].parent_id) + self.assertEqual( + "calendar.page.sidebar", surfaces["calendar.page.agenda"].parent_id + ) self.assertEqual("calendar.page", surfaces["calendar.page.workspace"].parent_id) self.assertEqual("calendar.page", surfaces["calendar.event-editor"].parent_id) - self.assertEqual("calendar.page.sidebar", surfaces["calendar.collection-editor"].parent_id) - self.assertEqual("calendar.collection-editor", surfaces["calendar.sync-status"].parent_id) + self.assertEqual( + "calendar.page.sidebar", surfaces["calendar.collection-editor"].parent_id + ) + self.assertEqual( + "calendar.collection-editor", surfaces["calendar.sync-status"].parent_id + ) self.assertEqual("calendar.sync-status", surfaces["calendar.outbox"].parent_id) - self.assertEqual("calendar.sync-status", surfaces["calendar.migration"].parent_id) + self.assertEqual( + "calendar.sync-status", surfaces["calendar.migration"].parent_id + ) def test_help_and_consequence_metadata_remain_published(self) -> None: topics = {topic.id: topic for topic in get_manifest().documentation} @@ -63,9 +79,9 @@ class CalendarInterfaceDocumentationContractTests(unittest.TestCase): quick_access = ( REPO_ROOT / "webui/src/features/calendar/CalendarQuickAccess.tsx" ).read_text(encoding="utf-8") - page = ( - REPO_ROOT / "webui/src/features/calendar/CalendarPage.tsx" - ).read_text(encoding="utf-8") + page = (REPO_ROOT / "webui/src/features/calendar/CalendarPage.tsx").read_text( + encoding="utf-8" + ) self.assertIn("const AGENDA_LIMIT = 7", quick_access) self.assertIn("launchContext.temporalContext", quick_access) @@ -75,13 +91,28 @@ class CalendarInterfaceDocumentationContractTests(unittest.TestCase): self.assertIn('parameters.get("quickAction") !== "create-event"', page) def test_webui_uses_shared_help_guard_and_confirmation_components(self) -> None: - event_dialog = (REPO_ROOT / "webui/src/features/calendar/CalendarEventDialog.tsx").read_text(encoding="utf-8") - collection_dialog = (REPO_ROOT / "webui/src/features/calendar/CalendarCollectionDialogs.tsx").read_text(encoding="utf-8") - settings_panel = (REPO_ROOT / "webui/src/features/calendar/CalendarSettingsPanel.tsx").read_text(encoding="utf-8") + event_dialog = ( + REPO_ROOT / "webui/src/features/calendar/CalendarEventDialog.tsx" + ).read_text(encoding="utf-8") + collection_dialog = ( + REPO_ROOT / "webui/src/features/calendar/CalendarCollectionDialogs.tsx" + ).read_text(encoding="utf-8") + settings_panel = ( + REPO_ROOT / "webui/src/features/calendar/CalendarSettingsPanel.tsx" + ).read_text(encoding="utf-8") - for component in ("ActionBlockerHint", "ConfirmDialog", "DocumentationHelpLink", "useUnsavedDraftGuard"): + for component in ( + "ActionBlockerHint", + "ConfirmDialog", + "DocumentationHelpLink", + "useUnsavedDraftGuard", + ): self.assertIn(component, event_dialog) - for component in ("ActionBlockerHint", "DocumentationHelpLink", "useUnsavedDraftGuard"): + for component in ( + "ActionBlockerHint", + "DocumentationHelpLink", + "useUnsavedDraftGuard", + ): self.assertIn(component, collection_dialog) for component in ("DocumentationHelpLink", "useUnsavedDraftGuard"): self.assertIn(component, settings_panel) diff --git a/webui/package.json b/webui/package.json index 0b23b41..f826e80 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/calendar-webui", - "version": "0.1.18", + "version": "0.1.20", "private": true, "type": "module", "main": "src/index.ts",