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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user