diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc5f51b..9b27ab6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,12 @@ jobs: - name: Run release guard audit run: COMMON_PLAT="$GITHUB_WORKSPACE/learning_ai_common_plat" bash scripts/release-guard-audit.sh + - name: Run UI drift ratchet (one-way gate vs scripts/ui-drift-baseline.json) + run: bash scripts/ui-drift-ratchet.sh + + - name: Run UI drift audit (report mode, informational) + run: bash scripts/ui-drift-audit.sh || true + backend: name: Backend — typecheck + test + build runs-on: ubuntu-latest diff --git a/docs/UI_UX_PLATFORM_CORE_ROADMAP.md b/docs/UI_UX_PLATFORM_CORE_ROADMAP.md index 47c2986..ff0314a 100644 --- a/docs/UI_UX_PLATFORM_CORE_ROADMAP.md +++ b/docs/UI_UX_PLATFORM_CORE_ROADMAP.md @@ -190,13 +190,16 @@ The review surface is the highest-value pilot because it is operator-critical an ### Phase UI8 — Remove Legacy Globals -- [ ] Remove or greatly reduce global `.surface-card`, `.surface-muted`, `.badge`, and `.input-shell` after migrated screens no longer depend on them. -- [ ] Keep only truly global layout/accessibility utilities such as focus-visible, `sr-only`, skip link, and motion preferences. -- [ ] Add CI guard for new raw UI drift: - - raw form controls outside approved primitives - - raw `.badge`/`.surface-*` usage +- [x] **CI guard for new raw UI drift** — ratchet shipped May 23, 2026. `scripts/ui-drift-ratchet.sh` reads tracked counts from `scripts/ui-drift-baseline.json` and FAILS in CI (`release-guards` job) if any of the four categories regress above baseline: + - raw form controls (`&2 + exit 2 +fi + +MODE="check" +if [[ "${1:-}" == "--update" ]]; then + MODE="update" +fi + +# Detect ripgrep availability with a grep fallback (same logic as ui-drift-audit.sh). +if command -v rg >/dev/null 2>&1; then + SEARCH_BACKEND="rg" +else + SEARCH_BACKEND="grep" +fi + +count_matches() { + local pattern="$1" + shift + local n + if [[ "$SEARCH_BACKEND" == "rg" ]]; then + n=$(rg -n "$pattern" "$@" --glob '!**/*.test.*' 2>/dev/null | wc -l | tr -d ' ') + else + n=$(grep -rnE "$pattern" "$@" \ + --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \ + --exclude="*.test.*" --exclude="*.spec.*" \ + --exclude-dir=node_modules --exclude-dir=.next 2>/dev/null | wc -l | tr -d ' ') + fi + echo "$n" +} + +# Read baseline values. Avoid jq dependency by using grep + sed. +read_baseline() { + local key="$1" + grep -E "\"$key\"" "$BASELINE_FILE" | head -1 | sed -E 's/.*: *([0-9]+).*/\1/' +} + +BASE_RAW=$(read_baseline raw_interactive_controls) +BASE_LEGACY=$(read_baseline legacy_global_surface_classes) +BASE_COLORS=$(read_baseline hardcoded_color_literals) +BASE_IMPORTS=$(read_baseline direct_bytelyst_ui_imports_outside_adapter) + +CUR_RAW=$(count_matches '/dev/null || echo 0) +CUR_IMPORTS=$((CUR_IMPORTS_RAW - ADAPTER_LINES)) +if [[ "$CUR_IMPORTS" -lt 0 ]]; then CUR_IMPORTS=0; fi + +if [[ "$MODE" == "update" ]]; then + cat >"$BASELINE_FILE" <&2 + echo " Migrate the new call site(s) to @bytelyst/ui primitives via" >&2 + echo " @/components/ui/Primitives, or — if migration lowered other" >&2 + echo " counts — run 'pnpm run audit:ui:ratchet:update' and commit." >&2 + exit 1 +fi + +echo +echo "OK — no UI drift regressions above baseline."