FN-6249: theme Compound Engineering textareas

Compound Engineering plugin textareas now use dashboard theme input styling.

- Apply dashboard surface, text, border, radius, font, focus, and placeholder tokens to CE free-text and guidance textareas.
- Add CSS token coverage for standard, degraded fallback, guidance, focus, and placeholder textarea states.

Files changed:
 .../src/dashboard/CompoundEngineeringView.css      | 19 ++++++
 .../src/dashboard/__tests__/theme-tokens.test.ts   | 71 ++++++++++++++++++++++
 2 files changed, 90 insertions(+)

Fusion-Task-Id: FN-6249

Fusion-Task-Lineage: d3c4dd30-1a94-4af2-a672-a2b814eb2c0d
This commit is contained in:
gsxdsm
2026-06-11 21:32:02 -07:00
parent c0ff360737
commit b0967984b3
2 changed files with 90 additions and 0 deletions

View File

@@ -277,10 +277,29 @@
flex-direction: column;
gap: 0.4rem;
}
.ce-flow-text textarea,
.ce-flow-guidance-row textarea {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-family: var(--font-primary);
outline: none;
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.ce-flow-text textarea {
width: 100%;
resize: vertical;
}
.ce-flow-text textarea:focus,
.ce-flow-guidance-row textarea:focus {
border-color: var(--todo);
box-shadow: var(--focus-ring);
}
.ce-flow-text textarea::placeholder,
.ce-flow-guidance-row textarea::placeholder {
color: var(--text-dim);
}
.ce-flow-confirm {
display: flex;
gap: 0.5rem;

View File

@@ -13,6 +13,31 @@ function selectorBlocks(selector: string): string[] {
return css.match(pattern) ?? [];
}
function selectorGroupBlocks(selector: string): string[] {
const blocks: string[] = [];
const rulePattern = /([^{}]+)\{([^}]*)\}/g;
for (const match of css.matchAll(rulePattern)) {
const selectors = match[1]
.split(",")
.map((candidate) => candidate.trim())
.filter(Boolean);
if (selectors.includes(selector)) {
blocks.push(match[0]);
}
}
return blocks;
}
function expectTextareaThemeTokens(selector: string, surfaceName: string) {
const blocks = selectorGroupBlocks(selector);
expect(blocks, `expected themed textarea block for ${surfaceName} (${selector})`).not.toHaveLength(0);
const block = blocks.join("\n");
expect(block, `expected ${surfaceName} to set theme surface background`).toMatch(/background:\s*var\(--surface\)\s*;/);
expect(block, `expected ${surfaceName} to set theme text color`).toMatch(/color:\s*var\(--text\)\s*;/);
expect(block, `expected ${surfaceName} to set theme border`).toMatch(/border:\s*1px\s+solid\s+var\(--border\)\s*;/);
expect(block, `expected ${surfaceName} not to rely on a transparent background`).not.toMatch(/background:\s*transparent\s*;/);
}
describe("CompoundEngineeringView theme tokens", () => {
it("does not use hardcoded legacy color fallbacks", () => {
const forbiddenPatterns = [
@@ -63,6 +88,52 @@ describe("CompoundEngineeringView theme tokens", () => {
expect(viewBlock).toMatch(/color:\s*var\(--text\)\s*;/);
});
it("themes every CE free-text textarea with dashboard input tokens", () => {
const textareaSurfaces = [
{
name: 'standard question/answer textarea (data-testid="ce-flow-text-input")',
selector: ".ce-flow-text textarea",
},
{
name: 'degraded chat fallback textarea (data-testid="ce-flow-degraded-input")',
selector: ".ce-flow-text textarea",
},
{
name: 'guidance textarea (data-testid="ce-flow-guidance-input")',
selector: ".ce-flow-guidance-row textarea",
},
];
for (const surface of textareaSurfaces) {
expectTextareaThemeTokens(surface.selector, surface.name);
}
});
it("themes CE textarea focus and placeholder states", () => {
const textFocusBlocks = selectorGroupBlocks(".ce-flow-text textarea:focus");
const guidanceFocusBlocks = selectorGroupBlocks(".ce-flow-guidance-row textarea:focus");
const textPlaceholderBlocks = selectorGroupBlocks(".ce-flow-text textarea::placeholder");
const guidancePlaceholderBlocks = selectorGroupBlocks(".ce-flow-guidance-row textarea::placeholder");
for (const [selector, blocks] of [
[".ce-flow-text textarea:focus", textFocusBlocks],
[".ce-flow-guidance-row textarea:focus", guidanceFocusBlocks],
] as const) {
expect(blocks, `expected focus block for ${selector}`).not.toHaveLength(0);
const block = blocks.join("\n");
expect(block, `expected ${selector} to use themed focus border`).toMatch(/border-color:\s*var\(--todo\)\s*;/);
expect(block, `expected ${selector} to use themed focus ring`).toMatch(/box-shadow:\s*var\(--focus-ring\)\s*;/);
}
for (const [selector, blocks] of [
[".ce-flow-text textarea::placeholder", textPlaceholderBlocks],
[".ce-flow-guidance-row textarea::placeholder", guidancePlaceholderBlocks],
] as const) {
expect(blocks, `expected placeholder block for ${selector}`).not.toHaveLength(0);
expect(blocks.join("\n"), `expected ${selector} to use dim text token`).toMatch(/color:\s*var\(--text-dim\)\s*;/);
}
});
it("does not use opacity to dim text selectors", () => {
const textDimmingSelectors = [
".ce-view-summary",