learning_ai_notes/scripts/ui-drift-ratchet.sh
saravanakumardb1 0c982de7e6 feat(web/ui8): remove legacy global classes + tighten audit regex + lock CI gate
UI8 closes the migration cycle started by UI0. The four legacy global
classes (.surface-card, .surface-muted, .badge, .input-shell) are
removed from web/src/app/globals.css and the CI ratchet now enforces
zero new occurrences across three of the four drift categories.

Changes:

1. Audit regex precision (scripts/ui-drift-audit.sh, scripts/ui-drift-ratchet.sh)

   The previous pattern 'className="[^"]*(badge|surface-card|surface-muted|input-shell)'
   matched the literal token anywhere inside className, which caused 21
   false positives against Tailwind arbitrary values like
   'bg-[color:var(--nl-surface-muted)]' where the legacy name appears
   inside a 'var(--nl-...)' reference.

   New pattern requires the legacy class to be a whole class token —
   either at the start of className, or preceded by a space, and
   followed by a space or closing quote. Result: 21 false positives
   eliminated; the ratchet now reports an honest 0 for the legacy
   category.

2. globals.css cleanup (web/src/app/globals.css)

   Removed .surface-card, .surface-muted, .badge, .input-shell rules.
   Only truly global utilities remain (typography, focus-visible,
   sr-only, skip-link, motion preferences, layout grids). A header
   comment documents that re-introductions should be solved at the
   call-site with a primitive, not by restoring the global rule.

3. Ratchet baseline (scripts/ui-drift-baseline.json)

   Final counts after UI5–UI8 across the session:
     raw interactive controls       14   (was 38 at start)
     legacy global surface classes  0    (was 92 at start)
     hardcoded color literals       0    (no change, was already 0)
     direct @bytelyst/ui imports    0    (no change, was already 0)

   The 14 remaining raw controls are intentional and tracked:
     NoteEditor toolbar buttons (10)
     ArtifactPanel hidden file input (1)
     search/page radio inputs (2)
     NoteVersionsPanel disclosure button (1)

4. CI gate (.github/workflows/ci.yml release-guards job)

   Documented that the ratchet is the canonical gate post-UI8: because
   legacy/colors/imports baselines are 0, any new occurrence in those
   three categories now fails CI. The strict-audit script is kept as
   a local diagnostic tool but not wired as a gate (would fail on the
   14 intentional raw controls).

5. Roadmap (docs/UI_UX_PLATFORM_CORE_ROADMAP.md)

   Marked UI5, UI6, UI7, UI8 all complete with per-phase commit hashes
   and explicit deliverables.

Cumulative migration impact (from initial baseline):
   raw interactive controls       38 → 14   (-24, -63%)
   legacy global surface classes  92 → 0    (-92, -100%)

Verified:
- pnpm run verify: backend 380/380, web 96/96, mobile 97/97
- bash scripts/ui-drift-ratchet.sh: all four categories at baseline
- bash scripts/ui-drift-audit.sh: only "Raw interactive controls"
  category has matches (intentional, tracked above)
- Live Docker stack at http://localhost:3050 still serves 200,
  backend health 200
2026-05-23 01:55:36 -07:00

131 lines
5.0 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# UI drift ratchet — a one-way gate that prevents UI legacy patterns from
# growing while UI5UI8 migrations are still in flight.
#
# How it works:
# - Reads tracked baseline counts from scripts/ui-drift-baseline.json
# - Counts current matches per category by re-using the same patterns
# as scripts/ui-drift-audit.sh
# - FAILS if any category COUNT is GREATER than its baseline
# - PASSES (and reports the delta) if counts equal or fall below baseline
#
# Usage:
# ./scripts/ui-drift-ratchet.sh # check against baseline (CI mode)
# ./scripts/ui-drift-ratchet.sh --update # rewrite baseline to current counts
# # (commit the result with a UI
# # migration that lowered them)
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"
BASELINE_FILE="scripts/ui-drift-baseline.json"
if [[ ! -f "$BASELINE_FILE" ]]; then
echo "FAIL: missing $BASELINE_FILE" >&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 '<button|<input|<textarea|<select' web/src/app web/src/components)
# Same word-boundary pattern as scripts/ui-drift-audit.sh — must not match
# inside Tailwind arbitrary values like `bg-[color:var(--nl-surface-muted)]`.
CUR_LEGACY=$(count_matches 'className="(badge|surface-card|surface-muted|input-shell)[" ]|className="[^"]* (badge|surface-card|surface-muted|input-shell)[" ]' web/src/app web/src/components)
CUR_COLORS=$(count_matches '#[0-9a-fA-F]{3,8}|rgba?\(' web/src/app web/src/components)
# For the adapter exclusion, filter out the adapter file post-hoc since we
# want a stable raw count.
CUR_IMPORTS_RAW=$(count_matches 'from "@bytelyst/ui"|from '\''@bytelyst/ui'\''' web/src/app web/src/components)
# Adapter file is allowed to import @bytelyst/ui; subtract one line per
# import line in Primitives.tsx so the ratchet doesn't drift just because
# the adapter grows its re-export list.
ADAPTER_LINES=$(grep -cE 'from "@bytelyst/ui"|from '\''@bytelyst/ui'\''' web/src/components/ui/Primitives.tsx 2>/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" <<JSON
{
"//": "Baseline UI drift counts. Updated $(date -u +%Y-%m-%dT%H:%M:%SZ) by scripts/ui-drift-ratchet.sh --update. Commit alongside the migration that lowered the counts.",
"raw_interactive_controls": $CUR_RAW,
"legacy_global_surface_classes": $CUR_LEGACY,
"hardcoded_color_literals": $CUR_COLORS,
"direct_bytelyst_ui_imports_outside_adapter": $CUR_IMPORTS
}
JSON
echo "Wrote new baseline to $BASELINE_FILE"
cat "$BASELINE_FILE"
exit 0
fi
# Compare and report.
failures=0
report() {
local title="$1"
local cur="$2"
local base="$3"
if [[ "$cur" -gt "$base" ]]; then
printf " ✗ %-50s %4d (baseline %d, +%d) — REGRESSION\n" "$title" "$cur" "$base" "$((cur - base))"
failures=$((failures + 1))
elif [[ "$cur" -lt "$base" ]]; then
printf " ↓ %-50s %4d (baseline %d, -%d) — progress, run --update\n" "$title" "$cur" "$base" "$((base - cur))"
else
printf " = %-50s %4d (at baseline)\n" "$title" "$cur"
fi
}
echo "UI drift ratchet (baseline: $BASELINE_FILE)"
report "raw interactive controls" "$CUR_RAW" "$BASE_RAW"
report "legacy global surface classes" "$CUR_LEGACY" "$BASE_LEGACY"
report "hardcoded color literals" "$CUR_COLORS" "$BASE_COLORS"
report "direct @bytelyst/ui imports outside adapter" "$CUR_IMPORTS" "$BASE_IMPORTS"
if [[ "$failures" -gt 0 ]]; then
echo
echo "FAIL: $failures category/categories regressed above baseline." >&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."