Fix: detect placeholder contrast (#790) (#799)

* Fix: detect placeholder contrast (#790)

`detect` never read `::placeholder` color, so pale placeholders passed. Score them with the same WCAG math as body text, without host class/clip heuristics.

Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: match descendant ::placeholder hosts (#790)

`.form ::placeholder` kept the ancestor as the host. Reuse the hover combinator star-fill so the color lands on the inputs inside.

Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: placeholder-shown and gradient alpha (#790)

Browser scans skip when :placeholder-shown is false, so a live filled field does not keep the HTML value attribute's empty state. Translucent placeholders flatten over each gradient stop before scoring.

Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: trailing combinator only for ::placeholder hosts (#790)

`star_empty_compounds` turned `.label + ::placeholder` into `.label *+*`. Fill only a trailing empty compound so adjacent-sibling hosts still match.

Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Abdul Wahab
2026-09-10 13:49:59 -07:00
committed by GitHub
co-authored by Cursor
parent 3e1f67c52c
commit cb56ed6c19
17 changed files with 688 additions and 137 deletions
+6 -1
View File
@@ -585,6 +585,7 @@ const __impeccableSnapshot = {
for (let id = 1; id < elements.length; id++) {
const el = elements[id];
const rec = { t: el.tagName };
const tag = rec.t;
const nsUri = el.namespaceURI || '';
const ns = __SNAP_NS[nsUri];
if (ns === undefined) { rec.n = 3; rec.nu = nsUri; } else if (ns !== 0) { rec.n = ns; }
@@ -615,6 +616,11 @@ const __impeccableSnapshot = {
if (content == null || content === '' || content === 'none') continue;
rec[key] = __SNAP_PSEUDO_PROPS.map(p => intern(ps[p]));
}
if ((tag === 'INPUT' || tag === 'TEXTAREA') && el.getAttribute('placeholder')) {
let ps;
try { ps = getComputedStyle(el, '::placeholder'); } catch { ps = null; }
if (ps) rec.ph = intern(ps.color);
}
if (typeof el.getBoundingClientRect === 'function') rec.r = __snapRect4(el.getBoundingClientRect());
rec.m = [
__snapNum(el.clientWidth), __snapNum(el.clientHeight), __snapNum(el.clientLeft),
@@ -632,7 +638,6 @@ const __impeccableSnapshot = {
if (typeof el.className !== 'string') rec.k = true;
const st = states.get(id);
if (st) rec.st = st;
const tag = rec.t;
if (tag === 'IMG' || tag === 'VIDEO' || tag === 'CANVAS' || tag === 'PICTURE') {
rec.md = {
nw: el.naturalWidth || 0, nh: el.naturalHeight || 0,