#!/usr/bin/env python3 """Enforce Core ownership of repeated WebUI toolbar, grid and dialog anatomy.""" from __future__ import annotations import pathlib import re import sys META_ROOT = pathlib.Path(__file__).resolve().parents[2] REPOS_ROOT = META_ROOT.parent CORE_INDEX = pathlib.Path("govoplan-core/webui/src/index.ts") RAW_ELEMENT = re.compile( r'<(?Pdiv|span|header|section|form)\b(?P[^>]*?)' r'\bclassName="(?P[^"]+)"', re.DOTALL, ) LEGACY_LAYOUT_TOKENS = { "form-grid", "admin-form-grid", "dashboard-grid", "settings-grid", "admin-dialog", "admin-dialog-wide", } CENTRAL_COMPONENTS = { "ActionToolbar": pathlib.Path( "govoplan-core/webui/src/components/ActionToolbar.tsx" ), "ToolbarGroup": pathlib.Path( "govoplan-core/webui/src/components/ActionToolbar.tsx" ), "ToolbarSpacer": pathlib.Path( "govoplan-core/webui/src/components/ActionToolbar.tsx" ), "ContentGrid": pathlib.Path( "govoplan-core/webui/src/components/ContentGrid.tsx" ), "FormGrid": pathlib.Path("govoplan-core/webui/src/components/ContentGrid.tsx"), "FormLayout": pathlib.Path( "govoplan-core/webui/src/components/ContentGrid.tsx" ), "GridItem": pathlib.Path("govoplan-core/webui/src/components/ContentGrid.tsx"), "FormSection": pathlib.Path( "govoplan-core/webui/src/components/FormSection.tsx" ), "DialogActions": pathlib.Path( "govoplan-core/webui/src/components/DialogAnatomy.tsx" ), "DialogForm": pathlib.Path( "govoplan-core/webui/src/components/DialogAnatomy.tsx" ), "DialogSection": pathlib.Path( "govoplan-core/webui/src/components/DialogAnatomy.tsx" ), } REQUIRED_CONSUMERS = { "ActionToolbar": ( pathlib.Path("govoplan-core/webui/src/components/WysiwygEditor.tsx"), pathlib.Path("govoplan-calendar/webui/src/features/calendar/CalendarPage.tsx"), pathlib.Path("govoplan-files/webui/src/features/files/FilesPage.tsx"), pathlib.Path("govoplan-forms/webui/src/features/forms/FormsPage.tsx"), pathlib.Path("govoplan-templates/webui/src/features/templates/TemplatesPage.tsx"), ), "ContentGrid": ( pathlib.Path("govoplan-core/webui/src/features/settings/SettingsPage.tsx"), pathlib.Path("govoplan-campaign/webui/src/features/campaigns/GlobalSettingsPage.tsx"), pathlib.Path("govoplan-notifications/webui/src/features/notifications/NotificationSettingsPanel.tsx"), ), "FormGrid": ( pathlib.Path("govoplan-core/webui/src/components/mail/MailServerSettingsPanel.tsx"), pathlib.Path("govoplan-calendar/webui/src/features/calendar/CalendarEventDialog.tsx"), pathlib.Path("govoplan-forms/webui/src/features/forms/FormDefinitionDialog.tsx"), pathlib.Path("govoplan-postbox/webui/src/features/postbox/PostboxAdminPanel.tsx"), ), "FormSection": ( pathlib.Path("govoplan-addresses/webui/src/features/addressbook/AddressBookPage.tsx"), pathlib.Path("govoplan-quick-access/webui/src/features/settings/QuickAccessSettingsPanel.tsx"), ), "DialogForm": ( pathlib.Path("govoplan-addresses/webui/src/features/addressbook/AddressBookPage.tsx"), pathlib.Path("govoplan-calendar/webui/src/features/calendar/CalendarEventDialog.tsx"), pathlib.Path("govoplan-records/webui/src/features/records/RecordsPage.tsx"), ), "DialogSection": ( pathlib.Path("govoplan-datasources/webui/src/features/datasources/DatasourcesPage.tsx"), pathlib.Path("govoplan-files/webui/src/features/files/components/FileShareDialog.tsx"), pathlib.Path("govoplan-templates/webui/src/features/templates/TemplatesPage.tsx"), ), } def source_paths() -> list[pathlib.Path]: paths: list[pathlib.Path] = [] for repository in sorted(REPOS_ROOT.glob("govoplan*")): source_root = repository / "webui" / "src" if source_root.is_dir(): paths.extend(sorted(source_root.rglob("*.tsx"))) return paths def relative(path: pathlib.Path) -> pathlib.Path: return path.relative_to(REPOS_ROOT) def raw_reason(classes: str) -> str | None: tokens = classes.split() legacy = sorted(set(tokens) & LEGACY_LAYOUT_TOKENS) if legacy: return f"legacy shared layout class {', '.join(legacy)}" toolbars = [ token for token in tokens if token == "admin-toolbar-row" or token.endswith("-toolbar") ] if toolbars: return f"raw toolbar class {', '.join(toolbars)}" dialog_forms = [token for token in tokens if token.endswith("dialog-form")] if dialog_forms: return f"raw dialog form class {', '.join(dialog_forms)}" return None def main() -> int: sources = source_paths() source_text = {relative(path): path.read_text(encoding="utf-8") for path in sources} errors: list[str] = [] for path, content in source_text.items(): for match in RAW_ELEMENT.finditer(content): reason = raw_reason(match.group("classes")) if reason is None: continue line = content.count("\n", 0, match.start()) + 1 errors.append( f"Raw {match.group('tag')} repeats shared anatomy ({reason}): {path}:{line}" ) for name, owner in CENTRAL_COMPONENTS.items(): definition = re.compile( rf"\b(?:function|class)\s+{name}\b|\bconst\s+{name}\s*=" ) for path, content in source_text.items(): if path != owner and definition.search(content): errors.append(f"Module-local {name} definition is not allowed: {path}") usage_counts: dict[str, int] = {} for name in CENTRAL_COMPONENTS: usage = re.compile(rf"<{name}\b") usage_counts[name] = sum(bool(usage.search(content)) for content in source_text.values()) for name, consumers in REQUIRED_CONSUMERS.items(): for path in consumers: absolute = REPOS_ROOT / path if not absolute.exists(): continue if f"<{name}" not in absolute.read_text(encoding="utf-8"): errors.append(f"Required shared {name} consumer regressed: {path}") core_index_path = REPOS_ROOT / CORE_INDEX if core_index_path.exists(): core_index = core_index_path.read_text(encoding="utf-8") for name in CENTRAL_COMPONENTS: if not re.search(rf"\b{name}\b", core_index): errors.append(f"Core must export {name} from @govoplan/core-webui.") dialog_path = REPOS_ROOT / "govoplan-core/webui/src/components/Dialog.tsx" if dialog_path.exists(): dialog_text = dialog_path.read_text(encoding="utf-8") if "