159 lines
5.5 KiB
Bash
Executable File
159 lines
5.5 KiB
Bash
Executable File
#!/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."
|