#!/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 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 "^\s*${slot}\s*=" "$THEME_FILE"; then log "⚠️ MISSING: \`$slot\` not explicitly defined in darkColors" missing=$((missing+1)) fi done 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."