feat: enforce shared UI foundations and pin reference journey
Dependency Audit / dependency-audit (push) Successful in 1m57s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Successful in 12m24s

This commit is contained in:
2026-08-18 21:32:25 +02:00
parent a1b80eda27
commit e2f505eeab
11 changed files with 445 additions and 64 deletions
+2
View File
@@ -91,6 +91,7 @@ PY
"$META_ROOT/tools/checks/check_dependency_boundaries.py"
"$PYTHON" "$META_ROOT/tools/checks/check-shared-webui-layouts.py"
"$PYTHON" "$META_ROOT/tools/checks/check-shared-webui-primitives.py"
"$PYTHON" "$META_ROOT/tools/checks/check-shared-webui-foundations.py"
"$PYTHON" -m unittest tests.test_module_system
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-connectors/tests
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-datasources/tests
@@ -121,6 +122,7 @@ cd "$ROOT/webui"
"$NPM" run test:mail-components
"$NPM" run test:module-capabilities
"$NPM" run test:module-permutations
"$NPM" run test:conformance
cd /mnt/DATA/git/govoplan-dataflow/webui
"$NPM" run test:structure
+117
View File
@@ -17,6 +17,70 @@ MODULE_NAME_PATTERN = re.compile(
r"[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*"
)
REQUIRED_DOCUMENTATION_TYPES = frozenset({"admin", "user"})
CANONICAL_PRODUCT_AREAS = {
"work": (
"i18n:govoplan-core.product_area.work",
"list-checks",
"i18n:govoplan-core.product_area.work_description",
10,
),
"services-cases": (
"i18n:govoplan-core.product_area.services_cases",
"landmark",
"i18n:govoplan-core.product_area.services_cases_description",
20,
),
"records-documents": (
"i18n:govoplan-core.product_area.records_documents",
"folder",
"i18n:govoplan-core.product_area.records_documents_description",
30,
),
"communication": (
"i18n:govoplan-core.product_area.communication",
"mail",
"i18n:govoplan-core.product_area.communication_description",
40,
),
"meetings-decisions": (
"i18n:govoplan-core.product_area.meetings_decisions",
"calendar",
"i18n:govoplan-core.product_area.meetings_decisions_description",
50,
),
"data-assurance": (
"i18n:govoplan-core.product_area.data_assurance",
"database-zap",
"i18n:govoplan-core.product_area.data_assurance_description",
60,
),
"people-responsibility": (
"i18n:govoplan-core.product_area.people_responsibility",
"users",
"i18n:govoplan-core.product_area.people_responsibility_description",
70,
),
}
# These surfaces are intentionally global, administrative, security-policy, or
# shell infrastructure. They remain discoverable through their dedicated shell
# affordance or through "All available tools" instead of a business area.
PRODUCT_AREA_EXEMPT_MODULES = frozenset(
{
"access",
"admin",
"audit",
"dashboard",
"docs",
"encryption",
"identity_trust",
"ops",
"policy",
"quick_access",
"search",
"tenancy",
"views",
}
)
def main() -> int:
@@ -113,6 +177,42 @@ def main() -> int:
)
continue
frontend = manifest.frontend
has_user_facing_surface = frontend is not None and bool(
frontend.routes
or frontend.public_routes
or frontend.nav_items
or frontend.settings_routes
)
if (
has_user_facing_surface
and not frontend.product_areas
and manifest.id not in PRODUCT_AREA_EXEMPT_MODULES
):
errors.append(
f"{repository_name}: user-facing module {manifest.id!r} has no "
"ProductAreaContribution and is not an explicit global/technical exemption"
)
if frontend is not None:
for contribution in frontend.product_areas:
expected = CANONICAL_PRODUCT_AREAS.get(contribution.id)
actual = (
contribution.label,
contribution.icon,
contribution.description,
contribution.order,
)
if expected is None:
errors.append(
f"{repository_name}: module {manifest.id!r} uses unknown product "
f"area {contribution.id!r}"
)
elif actual != expected:
errors.append(
f"{repository_name}: module {manifest.id!r} redefines canonical "
f"product area {contribution.id!r}; expected {expected!r}, found {actual!r}"
)
repository_root = manifest_path.parents[3]
if manifest.architecture is None:
if args.require_architecture:
@@ -142,6 +242,23 @@ def main() -> int:
print("\n".join(errors), file=sys.stderr)
return 1
contributed_product_areas = {
contribution.id
for manifest in manifests
if manifest.frontend is not None
for contribution in manifest.frontend.product_areas
}
missing_product_areas = sorted(
set(CANONICAL_PRODUCT_AREAS) - contributed_product_areas
)
if missing_product_areas:
print(
"Canonical product areas have no contributing module: "
+ ", ".join(missing_product_areas),
file=sys.stderr,
)
return 1
registry = PlatformRegistry()
try:
for manifest in manifests:
@@ -0,0 +1,118 @@
#!/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())
@@ -34,7 +34,6 @@ 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
@@ -64,4 +63,3 @@ 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