From 346e9672a66e29d8a3ab62f800291714425b22fc Mon Sep 17 00:00:00 2001 From: OoO Date: Tue, 5 May 2026 22:16:41 +0800 Subject: [PATCH] chore(observability): add CSS mirror sync helper --- docs/guides/observability_ui_governance.md | 8 +++- scripts/check_observability_ui.py | 2 +- scripts/quick_review.sh | 17 +++++++- scripts/sync_observability_css.py | 49 ++++++++++++++++++++++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 scripts/sync_observability_css.py diff --git a/docs/guides/observability_ui_governance.md b/docs/guides/observability_ui_governance.md index 4d1ae7a..db63c54 100644 --- a/docs/guides/observability_ui_governance.md +++ b/docs/guides/observability_ui_governance.md @@ -50,7 +50,13 @@ scripts/check_observability_suite.sh python3 scripts/check_observability_ui.py ``` -或透過既有 quick review 選單執行第 8 項完整 QA 套件、第 6 項靜態 UI guard: +如果修改了 `static/css/observability-system.css`,先同步 Flask 實際服務用的 mirror: + +```bash +python3 scripts/sync_observability_css.py +``` + +或透過既有 quick review 選單執行第 8 項完整 QA 套件、第 6 項靜態 UI guard、第 9 項 CSS mirror sync: ```bash ./scripts/quick_review.sh diff --git a/scripts/check_observability_ui.py b/scripts/check_observability_ui.py index 05e94f7..8736342 100644 --- a/scripts/check_observability_ui.py +++ b/scripts/check_observability_ui.py @@ -202,7 +202,7 @@ def scan_css() -> list[str]: web_text = web_path.read_text(encoding="utf-8") if web_text != text: findings.append( - f"{WEB_CSS_PATH}: must match {CSS_PATH} so production /static CSS is in sync" + f"{WEB_CSS_PATH}: must match {CSS_PATH} so production /static CSS is in sync; run `python3 scripts/sync_observability_css.py`" ) return findings diff --git a/scripts/quick_review.sh b/scripts/quick_review.sh index aba01a5..231f7b1 100755 --- a/scripts/quick_review.sh +++ b/scripts/quick_review.sh @@ -18,6 +18,7 @@ CODE_REVIEW_SCRIPT="$PROJECT_ROOT/scripts/code_review.py" OBSERVABILITY_UI_GUARD="$PROJECT_ROOT/scripts/check_observability_ui.py" OBSERVABILITY_PAGE_SMOKE="$PROJECT_ROOT/scripts/check_observability_pages.py" OBSERVABILITY_QA_SUITE="$PROJECT_ROOT/scripts/check_observability_suite.sh" +OBSERVABILITY_CSS_SYNC="$PROJECT_ROOT/scripts/sync_observability_css.py" # 顯示標題 echo -e "${BLUE}========================================${NC}" @@ -75,6 +76,16 @@ run_observability_qa_suite() { bash "$OBSERVABILITY_QA_SUITE" } +run_observability_css_sync() { + if [ ! -f "$OBSERVABILITY_CSS_SYNC" ]; then + echo -e "${RED}❌ AI觀測台 CSS 同步腳本不存在: $OBSERVABILITY_CSS_SYNC${NC}" + exit 1 + fi + + echo -e "${GREEN}🎨 同步 AI觀測台 CSS 到 Flask static path...${NC}" + python3 "$OBSERVABILITY_CSS_SYNC" +} + # 顯示選單 if [ $# -eq 0 ]; then echo -e "${YELLOW}請選擇操作:${NC}" @@ -86,8 +97,9 @@ if [ $# -eq 0 ]; then echo "6) AI觀測台 UI/UX 防回歸檢查" echo "7) AI觀測台 10頁線上巡檢" echo "8) AI觀測台完整 QA 套件" + echo "9) 同步 AI觀測台 CSS static mirror" echo "" - read -p "請輸入選項 (1-8): " choice + read -p "請輸入選項 (1-9): " choice case $choice in 1) @@ -126,6 +138,9 @@ if [ $# -eq 0 ]; then 8) run_observability_qa_suite ;; + 9) + run_observability_css_sync + ;; *) echo -e "${RED}❌ 無效選項${NC}" exit 1 diff --git a/scripts/sync_observability_css.py b/scripts/sync_observability_css.py new file mode 100644 index 0000000..3447242 --- /dev/null +++ b/scripts/sync_observability_css.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Sync the canonical observability CSS to the Flask-served static path. + +The project keeps the design-system source at: + static/css/observability-system.css + +Production Flask static serving currently exposes: + web/static/css/observability-system.css + +This helper keeps the two files byte-identical so deploys do not regress into +"HTML links the CSS but /static/css/observability-system.css is 404/stale". +""" + +from __future__ import annotations + +import shutil +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = ROOT / "static/css/observability-system.css" +TARGET = ROOT / "web/static/css/observability-system.css" + + +def main() -> int: + if not SOURCE.exists(): + print(f"ERROR: missing source CSS: {SOURCE.relative_to(ROOT)}") + return 1 + + TARGET.parent.mkdir(parents=True, exist_ok=True) + source_bytes = SOURCE.read_bytes() + target_bytes = TARGET.read_bytes() if TARGET.exists() else b"" + + if source_bytes == target_bytes: + print("Observability CSS sync: already in sync") + print(f"- source: {SOURCE.relative_to(ROOT)}") + print(f"- target: {TARGET.relative_to(ROOT)}") + return 0 + + shutil.copyfile(SOURCE, TARGET) + print("Observability CSS sync: synced") + print(f"- source: {SOURCE.relative_to(ROOT)}") + print(f"- target: {TARGET.relative_to(ROOT)}") + print(f"- bytes: {len(source_bytes)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())