fix(ui): calm composer focus treatment (#122232)

This commit is contained in:
Vyctor H. Brzezowski
2026-08-11 16:53:30 -03:00
committed by GitHub
parent 7a1d5a86c7
commit 6c485842b1
2 changed files with 90 additions and 2 deletions
@@ -0,0 +1,84 @@
import { expect, it } from "vitest";
import { installMockGateway } from "../test-helpers/control-ui-e2e.ts";
import { createControlUiE2eSuite } from "./control-ui-e2e-suite.test-support.ts";
const suite = createControlUiE2eSuite({
name: "Control UI chat composer focus",
});
suite.define(() => {
it("uses a visible neutral focus treatment in dark and light themes", async () => {
await suite.withPage({ viewport: { width: 1440, height: 900 } }, async ({ page }) => {
const gateway = await installMockGateway(page);
await page.goto(`${suite.server.baseUrl}chat`);
await gateway.waitForRequest("chat.startup");
const composer = page.locator(".agent-chat__input");
const textarea = composer.locator("textarea");
await composer.waitFor({ state: "visible" });
for (const theme of ["dark", "light"] as const) {
await page.evaluate((nextTheme) => {
document.documentElement.dataset.theme = "claw";
document.documentElement.dataset.themeMode = nextTheme;
}, theme);
await textarea.evaluate((element) => element.blur());
await page.waitForTimeout(150);
const unfocused = await composer.evaluate((element) => {
const style = getComputedStyle(element);
return { borderColor: style.borderColor, boxShadow: style.boxShadow };
});
await textarea.focus();
await page.waitForTimeout(150);
const focused = await composer.evaluate((element) => {
const style = getComputedStyle(element);
const parseRgb = (color: string): [number, number, number] => {
const values = color
.match(/[\d.]+/g)
?.slice(0, 3)
.map(Number);
if (!values || values.length !== 3) {
throw new Error(`expected an RGB color, received ${color}`);
}
return (
color.startsWith("color(srgb") ? values.map((value) => value * 255) : values
) as [number, number, number];
};
const luminance = (color: string): number => {
const linearize = (channel: number) => {
const normalized = channel / 255;
return normalized <= 0.04045
? normalized / 12.92
: ((normalized + 0.055) / 1.055) ** 2.4;
};
const channels = parseRgb(color);
return (
0.2126 * linearize(channels[0]) +
0.7152 * linearize(channels[1]) +
0.0722 * linearize(channels[2])
);
};
const borderLuminance = luminance(style.borderColor);
const surfaceLuminance = luminance(style.backgroundColor);
const borderChannels = parseRgb(style.borderColor);
const contrast =
(Math.max(borderLuminance, surfaceLuminance) + 0.05) /
(Math.min(borderLuminance, surfaceLuminance) + 0.05);
return {
borderColor: style.borderColor,
boxShadow: style.boxShadow,
contrast,
neutralChannelSpread: Math.max(...borderChannels) - Math.min(...borderChannels),
};
});
expect(focused.borderColor).not.toBe(unfocused.borderColor);
expect(focused.boxShadow).not.toBe(unfocused.boxShadow);
expect(focused.neutralChannelSpread).toBeLessThanOrEqual(24);
expect(focused.contrast).toBeGreaterThanOrEqual(3);
}
});
});
});
+6 -2
View File
@@ -2358,8 +2358,12 @@ openclaw-chat-video-player {
}
.agent-chat__input:focus-within {
border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
box-shadow: 0 0 0 3px var(--accent-subtle);
/* Focus is a neutral state, not an accent/status signal. This mix clears 3:1
against the composer surface in both claw themes; the halo keeps it legible. */
border-color: color-mix(in srgb, var(--muted) 72%, var(--border));
box-shadow:
0 0 0 3px color-mix(in srgb, var(--muted) 14%, transparent),
var(--shadow-sm);
}
.agent-chat__input--prefill-attention {