Closer/scripts/theme-scan.sh

159 lines
5.5 KiB
Bash
Raw Normal View History

#!/bin/bash
#
# CloserApp — automated theme-mismatch scanner (Pass C pre-check)
#
# ⛔ CLAUDE: You may improve this script whenever you discover a new failure mode
# for light/dark theme mismatches. Keep the script self-contained, runnable from
# the project root, and write findings to stdout + SCAN_OUTPUT. Update this header
# with any new patterns or exclusions you add.
#
# Usage:
# ./scripts/theme-scan.sh > /tmp/claude-theme-scan-$(date +%Y%m%d).md
# ./scripts/theme-scan.sh --json # (future improvement — machine-readable)
#
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
UI_DIR="$PROJECT_ROOT/app/src/main/java/app/closer/ui"
THEME_FILE="$PROJECT_ROOT/app/src/main/java/app/closer/ui/theme/Theme.kt"
SCAN_OUTPUT="${1:-}"
if [[ -z "$SCAN_OUTPUT" ]]; then
SCAN_OUTPUT="/tmp/claude-theme-scan-$(date +%Y%m%d).md"
fi
: > "$SCAN_OUTPUT"
log() {
echo "$1" | tee -a "$SCAN_OUTPUT"
}
log "# CloserApp Theme-Mismatch Scan — $(date)"
log "Project: $PROJECT_ROOT"
log ""
# -----------------------------------------------------------------------------
# Helper: find lines that are within N lines after a container composable starts.
# $1 = regex for container opening
# $2 = severity emoji + label
# $3 = grep-invert exclusions (optional)
# -----------------------------------------------------------------------------
scan_after_container() {
local pattern="$1"
local label="$2"
local exclude="${3:-Color\.Transparent}"
local tmp_containers=$(mktemp)
grep -rnE "$pattern" "$UI_DIR" --include="*.kt" > "$tmp_containers" || true
while IFS= read -r container; do
local file=$(echo "$container" | cut -d: -f1)
local lineno=$(echo "$container" | cut -d: -f2)
if [[ ! -f "$file" ]]; then continue; fi
local segment=$(sed -n "${lineno},$((lineno+7))p" "$file")
local match=$(echo "$segment" | grep -E '^\s*(color|containerColor|background)\s*=\s*Color(\.|\()' | grep -ivE "$exclude" | head -1 || true)
if [[ -n "$match" ]]; then
log "${label} ${file}:${lineno}"
log " ${match}"
fi
done < "$tmp_containers"
rm "$tmp_containers"
}
log "## Tier 1A — Container/surface colors that won't adapt (CRITICAL)"
log "Looks at Surface, Card, Dialog, AlertDialog, ModalBottomSheet, BottomSheet, Scaffold, LazyVerticalGrid, Box."
log ""
scan_after_container '(Surface|Card|Dialog|AlertDialog|ModalBottomSheet|BottomSheet|Scaffold|LazyVerticalGrid|Box)\s*\(' '🔴 CRITICAL' 'Color\.Transparent'
log ""
log "## Tier 1B — Modifier.background with hardcoded color (CRITICAL)"
grep -rnE 'Modifier\.background\(Color(\.|\()' "$UI_DIR" --include="*.kt" \
| grep -ivE 'Color\.Transparent' \
| grep -ivE 'Theme\.kt' \
| while IFS= read -r line; do
log "🔴 CRITICAL $line"
done || true
log ""
log "## Tier 1C — Component color overrides that won't adapt (MAJOR)"
grep -rnE '(buttonColors|TextFieldDefaults\.colors|TabRowDefaults\.colors|SwitchDefaults\.colors)\s*\(' "$UI_DIR" --include="*.kt" \
| grep -iE 'Color(\.|\()' \
| grep -ivE 'Color\.Transparent' \
| while IFS= read -r line; do
log "🟠 MAJOR $line"
done || true
grep -rnE '(Divider|HorizontalDivider)\s*\(' "$UI_DIR" --include="*.kt" \
| grep -iE 'color = Color(\.|\()' \
| while IFS= read -r line; do
log "🟠 MAJOR $line"
done || true
log ""
log "## Tier 1D — Text/Icon color hardcoded on themed surfaces (REVIEW)"
grep -rnE '^\s*color = Color\.(White|Black|(0x[0-9A-F]{8}))' "$UI_DIR" --include="*.kt" \
| grep -ivE 'Theme\.kt' \
| while IFS= read -r line; do
log "🟡 REVIEW $line"
done || true
log ""
log "## Tier 1E — Direct painterResource that bypasses BrandIllustration (MAJOR)"
grep -rnE 'painterResource\(R\.drawable\.(illustration_|pack_art_)' "$UI_DIR" --include="*.kt" \
| while IFS= read -r line; do
log "🟠 MAJOR $line"
done || true
log ""
log "## Tier 1F — Hardcoded border colors (REVIEW)"
grep -rnE 'Modifier\.border\([^)]*Color(\.|\()' "$UI_DIR" --include="*.kt" \
| grep -ivE 'Color\.Transparent' \
| while IFS= read -r line; do
log "🟡 REVIEW $line"
done || true
log ""
log "## Tier 1G — Hardcoded Brush/gradient stops (REVIEW)"
grep -rnE 'Brush\.(linear|vertical|horizontal|radial)Gradient' "$UI_DIR" --include="*.kt" \
| while IFS= read -r line; do
log "🟡 REVIEW $line"
done || true
log ""
log "## Tier 2 — Theme definition validation"
if [[ -f "$THEME_FILE" ]]; then
local slots_file=$(mktemp)
grep -oE '\b([a-z]+(Container|Surface|Primary|Secondary|Tertiary|Error|Scrim|Tint)?)\s*=' "$THEME_FILE" \
| grep -v '^//' \
| sed 's/\s*=//' > "$slots_file"
required=(
primary onPrimary primaryContainer onPrimaryContainer
secondary onSecondary secondaryContainer onSecondaryContainer
tertiary onTertiary tertiaryContainer onTertiaryContainer
background surface onBackground onSurface
surfaceVariant onSurfaceVariant outline outlineVariant
error onError errorContainer onErrorContainer
inverseSurface inverseOnSurface inversePrimary surfaceTint scrim
)
missing=0
for slot in "${required[@]}"; do
if ! grep -qE "^${slot}$" "$slots_file"; then
log "⚠️ MISSING: \`$slot\` not explicitly defined in darkColors"
missing=$((missing+1))
fi
done
rm "$slots_file"
if [[ $missing -eq 0 ]]; then
log "✅ darkColors has all required Material3 slots explicitly defined"
fi
else
log "⚠️ Could not find Theme.kt at $THEME_FILE"
fi
log ""
log "---"
log "End of scan. Next step: read findings above, file CRITICAL/MAJOR items to ClaudeReport.md, then run the visual sweep."