Enforce shared metric and description layouts
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Enforce Core ownership of repeated WebUI toolbar, grid and dialog anatomy."""
|
||||
"""Enforce Core ownership of repeated WebUI layout and dialog anatomy."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -11,6 +11,7 @@ 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")
|
||||
DIALOG_WIDTH_EXCEPTIONS = META_ROOT / "tools/checks/shared-webui-dialog-width-exceptions.txt"
|
||||
|
||||
RAW_ELEMENT = re.compile(
|
||||
r'<(?P<tag>div|span|header|section|form)\b(?P<attrs>[^>]*?)'
|
||||
@@ -24,6 +25,9 @@ LEGACY_LAYOUT_TOKENS = {
|
||||
"settings-grid",
|
||||
"admin-dialog",
|
||||
"admin-dialog-wide",
|
||||
"admin-details-grid",
|
||||
"detail-list",
|
||||
"metric-grid",
|
||||
}
|
||||
CENTRAL_COMPONENTS = {
|
||||
"ActionToolbar": pathlib.Path(
|
||||
@@ -55,6 +59,15 @@ CENTRAL_COMPONENTS = {
|
||||
"DialogSection": pathlib.Path(
|
||||
"govoplan-core/webui/src/components/DialogAnatomy.tsx"
|
||||
),
|
||||
"DescriptionList": pathlib.Path(
|
||||
"govoplan-core/webui/src/components/DescriptionList.tsx"
|
||||
),
|
||||
"DescriptionItem": pathlib.Path(
|
||||
"govoplan-core/webui/src/components/DescriptionList.tsx"
|
||||
),
|
||||
"MetricGrid": pathlib.Path(
|
||||
"govoplan-core/webui/src/components/MetricGrid.tsx"
|
||||
),
|
||||
}
|
||||
REQUIRED_CONSUMERS = {
|
||||
"ActionToolbar": (
|
||||
@@ -89,7 +102,42 @@ REQUIRED_CONSUMERS = {
|
||||
pathlib.Path("govoplan-files/webui/src/features/files/components/FileShareDialog.tsx"),
|
||||
pathlib.Path("govoplan-templates/webui/src/features/templates/TemplatesPage.tsx"),
|
||||
),
|
||||
"DescriptionList": (
|
||||
pathlib.Path("govoplan-access/webui/src/features/admin/UsersPanel.tsx"),
|
||||
pathlib.Path("govoplan-campaign/webui/src/features/campaigns/CampaignReportPage.tsx"),
|
||||
pathlib.Path("govoplan-docs/webui/src/features/docs/DocsPage.tsx"),
|
||||
pathlib.Path("govoplan-policy/webui/src/features/policy/ViewPoliciesPanel.tsx"),
|
||||
),
|
||||
"MetricGrid": (
|
||||
pathlib.Path("govoplan-core/webui/src/features/dashboard/DashboardPage.tsx"),
|
||||
pathlib.Path("govoplan-admin/webui/src/features/admin/ModuleManagementPanel.tsx"),
|
||||
pathlib.Path("govoplan-campaign/webui/src/features/operator/OperatorQueuePage.tsx"),
|
||||
pathlib.Path("govoplan-notifications/webui/src/features/notifications/NotificationSummaryWidget.tsx"),
|
||||
),
|
||||
}
|
||||
LEGACY_CSS_SELECTOR = re.compile(
|
||||
r"(?<![-\w])\.(?:admin-details-grid|detail-list|metric-grid)(?![-\w])"
|
||||
)
|
||||
CSS_BLOCK = re.compile(r"([^{}]+)\{([^{}]*)\}")
|
||||
CSS_COMMENT = re.compile(r"/\*.*?\*/", re.DOTALL)
|
||||
DIALOG_CLASS = re.compile(r"\.([A-Za-z0-9_-]*(?:dialog|modal)[A-Za-z0-9_-]*)", re.IGNORECASE)
|
||||
DIALOG_WIDTH = re.compile(r"(?:^|;)\s*(?:width|max-width)\s*:", re.MULTILINE)
|
||||
DIALOG_WIDTH_VALUE = re.compile(
|
||||
r"(?:^|;)\s*(?:width|max-width)\s*:\s*([^;]+)", re.MULTILINE
|
||||
)
|
||||
STANDARD_DIALOG_WIDTH = re.compile(r"\b(?:460|560|680|1040|1440)px\b")
|
||||
DIALOG_INTERNAL_SUFFIXES = (
|
||||
"-actions",
|
||||
"-body",
|
||||
"-close",
|
||||
"-content",
|
||||
"-field",
|
||||
"-fields",
|
||||
"-footer",
|
||||
"-form",
|
||||
"-header",
|
||||
"-title",
|
||||
)
|
||||
|
||||
|
||||
def source_paths() -> list[pathlib.Path]:
|
||||
@@ -101,6 +149,15 @@ def source_paths() -> list[pathlib.Path]:
|
||||
return paths
|
||||
|
||||
|
||||
def css_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("*.css")))
|
||||
return paths
|
||||
|
||||
|
||||
def relative(path: pathlib.Path) -> pathlib.Path:
|
||||
return path.relative_to(REPOS_ROOT)
|
||||
|
||||
@@ -123,9 +180,47 @@ def raw_reason(classes: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def normalized_css_selector(selector: str) -> str:
|
||||
return " ".join(selector.split())
|
||||
|
||||
|
||||
def dialog_width_exceptions(styles: dict[pathlib.Path, str]) -> dict[str, str]:
|
||||
exceptions: dict[str, str] = {}
|
||||
central_dialog_styles = pathlib.Path("govoplan-core/webui/src/styles/dialogs.css")
|
||||
for path, content in styles.items():
|
||||
if path == central_dialog_styles:
|
||||
continue
|
||||
without_comments = CSS_COMMENT.sub("", content)
|
||||
for match in CSS_BLOCK.finditer(without_comments):
|
||||
selector = normalized_css_selector(match.group(1))
|
||||
declarations = match.group(2)
|
||||
if not DIALOG_WIDTH.search(declarations):
|
||||
continue
|
||||
dialog_classes = DIALOG_CLASS.findall(selector)
|
||||
if not dialog_classes:
|
||||
continue
|
||||
if all(name.lower().endswith(DIALOG_INTERNAL_SUFFIXES) for name in dialog_classes):
|
||||
continue
|
||||
signature = f"{path}|{selector}"
|
||||
exceptions[signature] = declarations
|
||||
return exceptions
|
||||
|
||||
|
||||
def exception_baseline() -> set[str]:
|
||||
if not DIALOG_WIDTH_EXCEPTIONS.exists():
|
||||
return set()
|
||||
return {
|
||||
line.strip()
|
||||
for line in DIALOG_WIDTH_EXCEPTIONS.read_text(encoding="utf-8").splitlines()
|
||||
if line.strip() and not line.lstrip().startswith("#")
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
sources = source_paths()
|
||||
source_text = {relative(path): path.read_text(encoding="utf-8") for path in sources}
|
||||
styles = css_paths()
|
||||
style_text = {relative(path): path.read_text(encoding="utf-8") for path in styles}
|
||||
errors: list[str] = []
|
||||
|
||||
for path, content in source_text.items():
|
||||
@@ -138,6 +233,24 @@ def main() -> int:
|
||||
f"Raw {match.group('tag')} repeats shared anatomy ({reason}): {path}:{line}"
|
||||
)
|
||||
|
||||
for path, content in style_text.items():
|
||||
legacy_match = LEGACY_CSS_SELECTOR.search(CSS_COMMENT.sub("", content))
|
||||
if legacy_match:
|
||||
line = content.count("\n", 0, legacy_match.start()) + 1
|
||||
errors.append(f"Legacy shared layout selector is not allowed: {path}:{line}")
|
||||
|
||||
dialog_exceptions = dialog_width_exceptions(style_text)
|
||||
baseline = exception_baseline()
|
||||
for signature in sorted(set(dialog_exceptions) - baseline):
|
||||
errors.append(f"Unreviewed local dialog width; use Dialog size or register a justified exception: {signature}")
|
||||
for signature in sorted(baseline - set(dialog_exceptions)):
|
||||
errors.append(f"Stale dialog width exception can be removed: {signature}")
|
||||
for signature, declarations in sorted(dialog_exceptions.items()):
|
||||
width_values = " ".join(DIALOG_WIDTH_VALUE.findall(declarations))
|
||||
standard = STANDARD_DIALOG_WIDTH.search(width_values)
|
||||
if standard:
|
||||
errors.append(f"Local dialog width duplicates Core size {standard.group(0)}: {signature}")
|
||||
|
||||
for name, owner in CENTRAL_COMPONENTS.items():
|
||||
definition = re.compile(
|
||||
rf"\b(?:function|class)\s+{name}\b|\bconst\s+{name}\s*="
|
||||
@@ -178,9 +291,9 @@ def main() -> int:
|
||||
|
||||
migrated = ", ".join(
|
||||
f"{name}={usage_counts[name]} files"
|
||||
for name in ("ActionToolbar", "ContentGrid", "FormGrid", "FormSection", "DialogForm", "DialogSection")
|
||||
for name in ("ActionToolbar", "ContentGrid", "FormGrid", "FormSection", "DialogForm", "DialogSection", "MetricGrid", "DescriptionList")
|
||||
)
|
||||
print(f"Shared WebUI primitive contract passed: {migrated}; no raw legacy anatomy.")
|
||||
print(f"Shared WebUI primitive contract passed: {migrated}; {len(dialog_exceptions)} reviewed dialog width exceptions; no raw legacy anatomy.")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Reviewed module-specific Dialog and modal width selectors.
|
||||
#
|
||||
# Standard Core widths (460/560/680/1040/1440px) are never valid here.
|
||||
# Remove an entry when its consumer moves to Dialog size; additions require
|
||||
# an explicit design review and an inventory explanation.
|
||||
govoplan-addresses/webui/src/styles/addresses.css|.address-governance-dialog .dialog-panel
|
||||
govoplan-addresses/webui/src/styles/addresses.css|.address-import-dialog
|
||||
govoplan-addresses/webui/src/styles/addresses.css|.address-sync-dialog
|
||||
govoplan-addresses/webui/src/styles/addresses.css|.dialog-panel.address-contact-dialog
|
||||
govoplan-addresses/webui/src/styles/addresses.css|.dialog-panel.address-member-dialog
|
||||
govoplan-addresses/webui/src/styles/addresses.css|.dialog-panel.address-quality-dialog, .address-quality-dialog .dialog-panel
|
||||
govoplan-approvals/webui/src/styles/approvals.css|.approval-request-dialog, .approval-template-dialog
|
||||
govoplan-approvals/webui/src/styles/approvals.css|.approval-template-history-dialog
|
||||
govoplan-calendar/webui/src/styles/calendar.css|.calendar-delete-dialog
|
||||
govoplan-calendar/webui/src/styles/calendar.css|.calendar-migration-dialog
|
||||
govoplan-calendar/webui/src/styles/calendar.css|.calendar-outbox-dialog
|
||||
govoplan-calendar/webui/src/styles/calendar.css|.calendar-vevent-dialog
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.attachment-linking-detail-modal
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.attachment-rules-modal
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.campaign-content-library-dialog
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.campaign-copy-dialog
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.campaign-schedule-dialog
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.dialog-panel-wide
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.message-preview-modal
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.recipient-address-editor-modal
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.recipient-import-modal
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.template-action-dialog
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.template-expression-dialog
|
||||
govoplan-campaign/webui/src/styles/campaign-workspace.css|.template-preview-modal
|
||||
govoplan-cases/webui/src/styles/cases.css|.case-decision-dialog
|
||||
govoplan-cases/webui/src/styles/cases.css|.case-share-dialog
|
||||
govoplan-committee/webui/src/styles/committee.css|.committee-ballot-dialog
|
||||
govoplan-committee/webui/src/styles/committee.css|.committee-record-dialog
|
||||
govoplan-core/webui/src/styles/components.css|.dialog-panel.wysiwyg-editor-dialog
|
||||
govoplan-core/webui/src/styles/components.css|.guided-config-dialog
|
||||
govoplan-core/webui/src/styles/components.css|.password-generator-dialog
|
||||
govoplan-core/webui/src/styles/components.css|.unsaved-changes-dialog
|
||||
govoplan-core/webui/src/styles/layout.css|.concurrency-conflict-dialog
|
||||
govoplan-dashboard/webui/src/styles/dashboard.css|.dashboard-widget-config-dialog
|
||||
govoplan-dataflow/webui/src/styles/dataflow.css|.dataflow-decision-dialog
|
||||
govoplan-dataflow/webui/src/styles/dataflow.css|.dataflow-definition-dialog
|
||||
govoplan-dataflow/webui/src/styles/dataflow.css|.dataflow-run-dialog
|
||||
govoplan-dataflow/webui/src/styles/dataflow.css|.dataflow-source-dialog
|
||||
govoplan-dataflow/webui/src/styles/dataflow.css|.dataflow-triggers-dialog
|
||||
govoplan-datasources/webui/src/styles/datasources.css|.datasources-add-dialog
|
||||
govoplan-datasources/webui/src/styles/datasources.css|.datasources-governance-dialog
|
||||
govoplan-dist-lists/webui/src/styles/dist-lists.css|.dist-lists-entry-dialog
|
||||
govoplan-dist-lists/webui/src/styles/dist-lists.css|.dist-lists-explanation-dialog
|
||||
govoplan-files/webui/src/styles/file-manager.css|.file-dialog
|
||||
govoplan-files/webui/src/styles/file-manager.css|.file-dialog:has(.archive-preview)
|
||||
govoplan-files/webui/src/styles/file-manager.css|.file-dialog:has(.connector-sync-grid)
|
||||
govoplan-files/webui/src/styles/file-manager.css|.file-dialog:has(.file-share-dialog-content)
|
||||
govoplan-files/webui/src/styles/file-manager.css|.managed-file-chooser-dialog
|
||||
govoplan-forms-runtime/webui/src/styles/forms-runtime.css|.form-intake-dialog
|
||||
govoplan-forms/webui/src/styles/forms.css|.form-definition-dialog
|
||||
govoplan-organizations/webui/src/styles/organizations.css|.organization-upgrade-dialog
|
||||
govoplan-postbox/webui/src/styles/postbox.css|.postbox-dialog
|
||||
govoplan-postbox/webui/src/styles/postbox.css|.postbox-message-dialog
|
||||
govoplan-postbox/webui/src/styles/postbox.css|.postbox-template-dialog
|
||||
govoplan-projects/webui/src/styles/projects.css|.project-editor-dialog
|
||||
govoplan-records/webui/src/styles/records.css|.records-dialog
|
||||
govoplan-reporting/webui/src/styles/reporting.css|.reporting-drill-dialog
|
||||
govoplan-views/webui/src/styles/views.css|.views-assignment-dialog
|
||||
govoplan-workflow/webui/src/styles/workflow.css|.workflow-definition-dialog
|
||||
govoplan-workflow/webui/src/styles/workflow.css|.workflow-runs-dialog
|
||||
govoplan-workflow/webui/src/styles/workflow.css|.workflow-standard-comparison-dialog
|
||||
|
||||
Reference in New Issue
Block a user