diff --git a/services/ai_surface_html_readback_service.py b/services/ai_surface_html_readback_service.py index db46154..b1fd223 100644 --- a/services/ai_surface_html_readback_service.py +++ b/services/ai_surface_html_readback_service.py @@ -416,8 +416,30 @@ def _route_sources_for_template(root: Path, template_name: str) -> list[str]: def _evaluate_site_template(root: Path, path: Path) -> dict[str, Any]: relpath = _relative_template_path(root, path) - html = path.read_text(encoding="utf-8") high_priority = _is_high_priority_template(relpath) + try: + html = path.read_text(encoding="utf-8") + except UnicodeDecodeError as exc: + return { + "template": relpath, + "family": _template_family(relpath), + "priority": "P1" if high_priority else "P2", + "status": "critical", + "route_sources": _route_sources_for_template(root, relpath), + "guardrails": { + "compact_marker_present": False, + "benchmark_marker_present": False, + "professional_marker_present": False, + }, + "finding_count": 1, + "findings": [{ + "type": "template_encoding_error", + "severity": "critical", + "encoding": "utf-8", + "error": str(exc)[:300], + }], + "next_machine_action": "build_sitewide_ui_ux_repair_package", + } compact_marker_present = "data-density-guardrail=" in html benchmark_marker_present = "data-benchmark-guardrail=" in html professional_marker_present = any(marker in html for marker in PROFESSIONAL_GUARDRAIL_MARKERS) @@ -569,6 +591,15 @@ def build_sitewide_ui_ux_repair_package( "Add first-viewport status, next action, compact density marker, and evidence-on-demand pattern." ), }) + if finding.get("type") == "template_encoding_error": + actions.append({ + "action": "normalize_template_encoding_utf8", + "target_template": surface.get("template"), + "safe_apply_hint": ( + "Convert the template to UTF-8, preserve visible Traditional Chinese copy, " + "then rerun sitewide UI/UX inventory." + ), + }) repair_items.append({ "template": surface.get("template"), "family": surface.get("family"), diff --git a/tests/test_ai_automation_smoke_service.py b/tests/test_ai_automation_smoke_service.py index e5dbe5f..9e3d231 100644 --- a/tests/test_ai_automation_smoke_service.py +++ b/tests/test_ai_automation_smoke_service.py @@ -394,6 +394,29 @@ def test_sitewide_ui_ux_repair_package_prioritizes_controlled_template_repairs() assert package["repair_items"][0]["controlled_actions"] +def test_sitewide_ui_ux_agent_reports_non_utf8_templates(tmp_path): + from services.ai_surface_html_readback_service import ( + build_sitewide_ui_ux_agent_inventory, + build_sitewide_ui_ux_repair_package, + ) + + template_dir = tmp_path / "templates" + template_dir.mkdir() + (template_dir / "legacy_dashboard.html").write_bytes(b"\xa3 legacy dashboard") + + inventory = build_sitewide_ui_ux_agent_inventory(root=tmp_path) + issue = inventory["issue_surfaces"][0] + package = build_sitewide_ui_ux_repair_package(source_inventory=inventory) + + assert inventory["status"] == "warning" + assert issue["status"] == "critical" + assert issue["findings"][0]["type"] == "template_encoding_error" + assert package["repair_items"][0]["controlled_actions"][0]["action"] == ( + "normalize_template_encoding_utf8" + ) + assert package["safety"]["writes_database"] is False + + def test_surface_html_readback_check_is_part_of_ai_smoke(monkeypatch): from services import ai_automation_smoke_service as smoke