diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index be5e19fc..7c0b9076 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -65,6 +65,9 @@ jobs: - name: Run ktlintCheck run: ./gradlew ktlintCheck + - name: Run checkColorLiterals (hardcoded Color guard, issue #622) + run: ./gradlew checkColorLiterals + - name: Upload ktlint reports if: always() uses: actions/upload-artifact@v7 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e99552cf..a13bd862 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -209,6 +209,75 @@ tasks.register("ktlintFormat") { commandLine("sh", "-c", "ktlint -F \"app/src/**/*.kt\"") } +// ── Hardcoded Color Guard (issue #622) ──────────────────────────────────── +// +// Fails the build if a hardcoded Color literal (Color(0x...) or named +// Color.White/Black/Red/Green/Gray/etc.) is introduced outside the theme +// module, *Preview.kt files, and _test sources. Pairing/Auth screens are +// exempt via the per-path overlay list below. +// +// Acceptable replacements (see docs/color-hardcoded-audit.md): +// - MaterialTheme.colorScheme. +// - LocalHermesStatusColors.current. +// - Color.Transparent / Color.Unspecified (intentional) + +// Resolve paths at configuration time (config-cache compatible). +val colorGuardSrcDir = layout.projectDirectory + .dir("src/main/java/com/m57/hermescontrol") +val colorGuardExemptions = listOf( + "/theme/", + "PairingScreen.kt", + "AuthLoginScreen.kt", + "Preview.kt", +) +val colorGuardHexPattern = Regex("""Color\(\s*0x[0-9A-Fa-f]{6,8}\s*\)""") +val colorGuardNamedPattern = Regex("""Color\.(White|Black|Red|Green|Gray|LightGray|DarkGray|Yellow|Blue|Cyan|Magenta)\b""") + +tasks.register("checkColorLiterals") { + group = "verification" + description = "Fails if hardcoded Color(...) literals appear outside theme/, *Preview.kt, and _test." + + val srcDir = colorGuardSrcDir + val exemptions = colorGuardExemptions + val hexPattern = colorGuardHexPattern + val namedPattern = colorGuardNamedPattern + + doLast { + val offenders = mutableListOf>() + srcDir.asFile.walkTopDown().filter { it.isFile && it.extension == "kt" }.forEach { file -> + if (exemptions.any { file.absolutePath.contains(it) }) return@forEach + file.useLines { lines -> + lines.forEachIndexed { idx, raw -> + val line = raw.trim() + if (line.startsWith("import ")) return@forEachIndexed + if (line.startsWith("//") || line.startsWith("*")) return@forEachIndexed + if (hexPattern.containsMatchIn(line) || namedPattern.containsMatchIn(line)) { + offenders.add(file.absolutePath to (idx + 1)) + } + } + } + } + + if (offenders.isNotEmpty()) { + val report = offenders.joinToString("\n") { (path, line) -> + " - $path:$line" + } + throw GradleException( + "Hardcoded Color literals found outside theme/ + *Preview.kt + _test:\n$report\n\n" + + "Replace with MaterialTheme.colorScheme., " + + "LocalHermesStatusColors.current., or " + + "Color.Transparent / Color.Unspecified (intentional).\n" + + "See docs/color-hardcoded-audit.md.", + ) + } + logger.lifecycle("checkColorLiterals: no hardcoded Color literals found. ✅") + } +} + +tasks.named("check") { + dependsOn("checkColorLiterals") +} + tasks.withType { useJUnitPlatform() -} +} \ No newline at end of file diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt index 1b92976e..41d643d8 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt @@ -1886,7 +1886,7 @@ private fun ToolBubble( var expanded by remember { mutableStateOf(false) } var showRawJson by remember { mutableStateOf(false) } val chipColor = if (isDarkTheme) ToolChipColor else ToolChipColorLight - val contentColor = if (isDarkTheme) Color.White else Color.Black + val contentColor = MaterialTheme.colorScheme.onSurfaceVariant val statusColors = LocalHermesStatusColors.current val parsed = diff --git a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt index 662fd590..ce6a47f7 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt @@ -806,7 +806,7 @@ fun SessionsScreen( CircularProgressIndicator( modifier = Modifier.size(18.dp), strokeWidth = 2.dp, - color = Color.White, + color = statusColors.onError, ) } else { Icon(