#!/usr/bin/env python3 """Enforce Core ownership of WebUI visual foundations.""" from __future__ import annotations import pathlib import re import sys META_ROOT = pathlib.Path(__file__).resolve().parents[2] REPOS_ROOT = META_ROOT.parent TOKENS_PATH = REPOS_ROOT / "govoplan-core/webui/src/styles/tokens.css" RAW_HEX_COLOR = re.compile(r"#[0-9a-fA-F]{3,8}\b") RAW_COLOR_FUNCTION = re.compile(r"\b(?:rgb|rgba|hsl|hsla)\((?!\s*var\()", re.IGNORECASE) RADIUS_DECLARATION = re.compile(r"border-radius\s*:\s*([^;}]+)") MEDIA_MAX_WIDTH = re.compile(r"@media[^\n{]*\(max-width\s*:\s*(\d+)px\)") RESPONSIVE_BANDS = {560, 600, 680, 760, 900, 1100, 1280} REQUIRED_TOKENS = { "--radius-hairline", "--radius-tight", "--radius-xs", "--radius-sm", "--radius-compact", "--radius-md", "--radius-lg", "--radius-xl", "--radius-round", "--radius-pill", "--shadow-drawer-side", "--shadow-drawer-bottom", "--action-primary-bg", "--action-primary-border", "--action-primary-text", "--action-danger-bg", "--action-danger-text", "--badge-accent-text", "--data-category-blue", "--data-category-green", "--data-category-amber", "--data-category-purple", "--data-category-rose", *(f"--data-series-{index}" for index in range(1, 9)), } def line_number(source: str, offset: int) -> int: return source.count("\n", 0, offset) + 1 def css_files() -> list[pathlib.Path]: files: list[pathlib.Path] = [] for repository in sorted(REPOS_ROOT.glob("govoplan-*")): styles = repository / "webui/src" if styles.is_dir(): files.extend(sorted(styles.rglob("*.css"))) return files def display_path(path: pathlib.Path) -> str: return str(path.relative_to(REPOS_ROOT)) def main() -> int: errors: list[str] = [] tokens = TOKENS_PATH.read_text(encoding="utf-8") for token in sorted(REQUIRED_TOKENS): if f"{token}:" not in tokens: errors.append(f"{display_path(TOKENS_PATH)}: missing required foundation token {token}") files = css_files() for path in files: source = path.read_text(encoding="utf-8") owns_literals = path == TOKENS_PATH if not owns_literals: for pattern, label in ( (RAW_HEX_COLOR, "raw color"), (RAW_COLOR_FUNCTION, "raw color function"), ): for match in pattern.finditer(source): errors.append( f"{display_path(path)}:{line_number(source, match.start())}: " f"{label} must use a Core theme token" ) for match in RADIUS_DECLARATION.finditer(source): value = match.group(1).strip() if "var(" not in value and value not in {"0", "inherit", "initial", "unset"}: errors.append( f"{display_path(path)}:{line_number(source, match.start())}: " f"border radius {value!r} must use a Core radius token" ) for match in MEDIA_MAX_WIDTH.finditer(source): width = int(match.group(1)) if width not in RESPONSIVE_BANDS: errors.append( f"{display_path(path)}:{line_number(source, match.start())}: " f"{width}px is not a shared responsive band; use one of " f"{', '.join(f'{value}px' for value in sorted(RESPONSIVE_BANDS))}" ) if errors: print("\n".join(errors)) return 1 print( "Shared WebUI foundation contract passed for " f"{len(files)} stylesheets and {len(RESPONSIVE_BANDS)} responsive bands." ) return 0 if __name__ == "__main__": raise SystemExit(main())