feat(chat): restyle the composer as a single card (#1114)
This commit is contained in:
1 parent
528ec4f28f
commit
e5adfcf571
7 files changed
+671
-303
No files matched your search
@@ -0,0 +1,76 @@
|
||||
package com.m57.hermescontrol.ui.chat.components
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import com.m57.hermescontrol.theme.ThemePreset
|
||||
import com.m57.hermescontrol.theme.resolveColorScheme
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Contrast gate for the chat composer across every preset in both modes.
|
||||
*
|
||||
* - Primary text and control labels (onSurface) >= 4.5:1 — WCAG AA text.
|
||||
* - Secondary text (placeholder, reasoning level) >= 3:1 — same bar as the
|
||||
* full-bleed header gate in ThemePaletteTest.
|
||||
* - Action button against the card, and its glyph against the button >= 3:1.
|
||||
* - Flat controls and the card edge must stay visibly distinct (>= 1.15:1).
|
||||
* Their labels and icons carry the WCAG 1.4.11 contrast; this floor catches
|
||||
* presets whose surface tiers collapse (Nord's High == Highest rendered the
|
||||
* controls invisible).
|
||||
*/
|
||||
class ComposerPaletteTest {
|
||||
private fun contrast(
|
||||
a: Color,
|
||||
b: Color,
|
||||
): Float {
|
||||
val l1 = a.luminance()
|
||||
val l2 = b.luminance()
|
||||
return (maxOf(l1, l2) + 0.05f) / (minOf(l1, l2) + 0.05f)
|
||||
}
|
||||
|
||||
private fun forEveryPresetMode(block: (String, ComposerPalette, Color) -> Unit) {
|
||||
ThemePreset.entries.forEach { preset ->
|
||||
listOf(true, false).forEach { dark ->
|
||||
val scheme = resolveColorScheme(preset, darkTheme = dark)
|
||||
block("$preset dark=$dark", composerPalette(scheme), scheme.background)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertContrast(
|
||||
label: String,
|
||||
a: Color,
|
||||
b: Color,
|
||||
min: Float,
|
||||
) {
|
||||
val ratio = contrast(a, b)
|
||||
assertTrue("$label contrast %.2f must be >= $min".format(ratio), ratio >= min)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun textMeetsContrastInEveryPresetMode() {
|
||||
forEveryPresetMode { mode, p, _ ->
|
||||
assertContrast("$mode input text/card", p.text, p.card, 4.5f)
|
||||
assertContrast("$mode placeholder/card", p.placeholder, p.card, 3f)
|
||||
assertContrast("$mode control label/control", p.onControl, p.control, 4.5f)
|
||||
assertContrast("$mode reasoning level/control", p.onControlVariant, p.control, 3f)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun actionButtonMeetsContrastInEveryPresetMode() {
|
||||
forEveryPresetMode { mode, p, _ ->
|
||||
assertContrast("$mode action/card", p.action, p.card, 3f)
|
||||
assertContrast("$mode action glyph/action", p.onAction, p.action, 3f)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun controlsAndCardEdgeStayVisibleInEveryPresetMode() {
|
||||
forEveryPresetMode { mode, p, background ->
|
||||
assertContrast("$mode control/card", p.control, p.card, 1.15f)
|
||||
assertContrast("$mode card border/background", p.cardBorder, background, 1.15f)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.m57.hermescontrol.ui.chat.components
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Unit tests for [composerModelLabel] — the label shown in the composer's
|
||||
* model/reasoning pill. Only the "custom:" provider marker is dropped; the
|
||||
* provider stays so the same model from different providers never collides.
|
||||
*/
|
||||
class ComposerToolbarTest {
|
||||
@Test
|
||||
fun composerModelLabel_keepsProviderToDisambiguateSameModel() {
|
||||
assertEquals("openai/gpt-5.5", composerModelLabel("openai/gpt-5.5"))
|
||||
assertEquals("copilot/gpt-5.5", composerModelLabel("copilot/gpt-5.5"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun composerModelLabel_dropsOnlyTheCustomMarker() {
|
||||
assertEquals("my-provider/glm-5.3", composerModelLabel("custom:my-provider/glm-5.3"))
|
||||
assertEquals("openrouter/openai/gpt-5.5", composerModelLabel("openrouter/openai/gpt-5.5"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun composerModelLabel_leavesModelTagsAndPlainIdsAlone() {
|
||||
assertEquals("ollama/llama3:8b", composerModelLabel("ollama/llama3:8b"))
|
||||
assertEquals("llama3:8b", composerModelLabel("llama3:8b"))
|
||||
assertEquals("gpt-5", composerModelLabel("gpt-5"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun composerModelLabel_bareCustomMarker_fallsBackToFullId() {
|
||||
assertEquals("custom:/glm-5.3", composerModelLabel("custom:/glm-5.3"))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user