fix(sitesmith): judge ContactForm labels against the form's own card, not the section

The ContactForm component paints its own card (default style.backgroundColor = bg token).
Contrast repair judged labelColor against the section behind it, so white labels on the
white default card (inside a dark section) went unrepaired (eval r3 case 6, ttm-likes).
A ContactForm with no style prop now resolves to the bg token; an explicit style without
a background stays transparent and is still judged against the section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shadowdaoandClaude Opus 5.5 committed 2026-11-01 18:20:58 -08:00
1 parent 69568e5979
commit 37935abad8
2 files changed
+51 -2

No files matched your search

+47 -2
View File
@@ -385,15 +385,60 @@ describe('color-mix() surfaces and ContactForm inputs (eval 2026-11-01: placehol
});
test('ContactForm label colour is checked against the section it sits on', () => {
const resp = replace(n('Section', { style: { backgroundColor: '#0d0b14' } }, [
n('ContactForm', { labelColor: 'var(--wsb-text)', fields: [] }),
n('ContactForm', { style: {}, labelColor: 'var(--wsb-text)', fields: [] }), // explicit style without a background = transparent form
]));
const out = repairResponseContrast(resp, D);
const f = firstTree(out.response).nodes![0].props as any;
expect(f.labelColor).not.toBe('var(--wsb-text)');
});
test('ContactForm with the default (token) input surface is left alone', () => {
const resp = replace(n('Section', { style: { backgroundColor: '#0d0b14' } }, [n('ContactForm', { labelColor: '#ffffff', fields: [] })]));
const resp = replace(n('Section', { style: { backgroundColor: '#0d0b14' } }, [n('ContactForm', { style: {}, labelColor: '#ffffff', fields: [] })]));
const out = repairResponseContrast(resp, D);
expect(firstTree(out.response).nodes![0].props.inputTextColor).toBeUndefined();
});
});
describe('ContactForm labels/inputs are judged against the FORM card, not the section (eval r3 case 6 ttm-likes)', () => {
// Real shape: dark gradient section; ContactForm with NO style prop, so the component's default card
// (style.backgroundColor = var(--wsb-bg), light) applies; labelColor/inputTextColor = var(--wsb-bg) (white).
const darkSection = (form: SerializedTreeNode) => replace(n('Section', {
style: { background: 'linear-gradient(135deg, #0d0b14 30%, color-mix(in srgb, var(--wsb-secondary) 40%, #0d0b14) 100%)' },
}, [n('Container', { style: { padding: '0px' } }, [form])]));
const formOf = (r: SitesmithResponse) => firstTree(r).nodes![0].nodes![0].props as any;
test('case-6: white labels on the default (light) form card inside a dark section are repaired to read on the card', () => {
const out = repairResponseContrast(darkSection(n('ContactForm', {
fields: [], labelColor: 'var(--wsb-bg)', inputTextColor: 'var(--wsb-bg)',
inputBg: 'color-mix(in srgb, var(--wsb-bg) 8%, #0d0b14)',
})), D);
const f = formOf(out.response);
expect(ratio(f.labelColor, 'var(--wsb-bg)')).toBeGreaterThanOrEqual(4.5);
expect(out.adjustments.some((a) => a.node === 'ContactForm' && a.from === 'var(--wsb-bg)')).toBe(true);
// typed text stays on the dark input surface (unchanged: white on near-black passes)
expect(f.inputTextColor).toBe('var(--wsb-bg)');
});
test('an explicit light form card via style.backgroundColor literal is honoured too', () => {
const out = repairResponseContrast(darkSection(n('ContactForm', {
fields: [], style: { backgroundColor: '#fafafa' }, labelColor: '#ffffff',
})), D);
expect(ratio(formOf(out.response).labelColor, '#fafafa')).toBeGreaterThanOrEqual(4.5);
});
test('a form card that is dark (token surface) keeps light labels untouched', () => {
const out = repairResponseContrast(darkSection(n('ContactForm', {
fields: [], style: { backgroundColor: '#111827' }, labelColor: '#ffffff',
})), D);
expect(formOf(out.response).labelColor).toBe('#ffffff');
expect(out.adjustments).toEqual([]);
});
test('no regression: a transparent form (explicit style without a background) is still judged against the dark section', () => {
const out = repairResponseContrast(darkSection(n('ContactForm', {
fields: [], style: { padding: '32px' }, labelColor: 'var(--wsb-text)',
})), D);
expect(formOf(out.response).labelColor).not.toBe('var(--wsb-text)');
});
test('labels already readable on the (default light) form card are not changed', () => {
const out = repairResponseContrast(darkSection(n('ContactForm', { fields: [], labelColor: 'var(--wsb-text)' })), D);
expect(formOf(out.response).labelColor).toBe('var(--wsb-text)');
expect(out.adjustments).toEqual([]);
});
});
+4
View File
@@ -311,6 +311,10 @@ function ownBg(name: string, props: Record<string, any>, design: SiteDesign, nam
if (b !== 'none') return b;
}
if (BGCOLOR_DEFAULTS_TO_TEXT.has(name)) return cssBg(tokenRef('text'), design);
// ContactForm paints its own card: with no `style` prop the component default applies (style.backgroundColor =
// the bg token, ContactForm.tsx craft.props), so labels sit on THAT, not on the section behind the card.
// An explicit `style` without a background is a transparent form and falls through to the section.
if (name === 'ContactForm' && props.style === undefined) return cssBg(tokenRef('bg'), design);
return 'none';
}