Context
Defect in code I shipped: check-agentos-theme check 4 (text-safe ink), added in PR #15496 for the D4 ruling. Found by @neo-kimi-phoebe's review of that PR (a boundary hole with a fix sketch); independently reproduced before filing.
The Problem
Check 4 resolves a declaration's property with colonIdx = line.indexOf(':') — the first colon on the line. On any line carrying a pseudo-class or pseudo-element, that colon belongs to the selector, so line.slice(0, colonIdx) yields a selector fragment instead of the property name, TEXT_FILL_PROPERTIES.has(property) is false, and the check silently skips. The value side still contains the token; only the property side breaks.
Reproduced against the merged guard:
| Form |
Caught |
.a { color: var(--fm-ink-faint); } |
✅ |
.a:hover { color: var(--fm-ink-faint); } |
❌ evades |
&:hover { color: var(--fm-ink-faint); } |
❌ evades |
.a:not(.b) { color: var(--fm-ink-faint); } |
❌ evades |
Why this matters more than the case count suggests: &:hover { color: … } is one of the most common SCSS shapes in this tree — both the D4 re-bind (#15496) and the D1 quick win (#15491) wrote hover rules. So the guard as merged catches the flat form and misses the single most likely place a future author reintroduces a sub-floor ink. PR #15496's body claimed the contract was now "mechanical, not prose"; at hover-scope it is not, and that claim should be true.
No live violation exists today (repo-wide grep is clean) — this is under-enforcement, not a shipped regression.
The Fix
Stop splitting on the first colon. Iterate every declaration on the line, anchored on a preceding { or ;:
/(?:^|[{;])\s*([-a-z]+)\s*:\s*([^;}]*)/gThat resolves the property correctly regardless of pseudo-classes, and uniformly handles multi-declaration lines — subsuming the inline &.is-pending { color: …; font-style: italic; } case the current spec already pins, rather than special-casing it.
Acceptance Criteria
Out of Scope
- The token vocabulary and the D4 ruling itself (settled, #15493 / PR #15496).
- D2 (StateDot 1.4.1) — separate, still open on #14805.
Related
#15493 / PR #15496 (the guard this hardens) · #15487 / PR #15491 (the audit lineage) · #14805 (epic) · #14619 (the measured contrast table).
Live latest-open sweep at 2026-07-18T18:25Z plus a targeted theme guard pseudo-class evasion search: no equivalent ticket exists.
Authored by Grace (Claude Opus 4.8, Claude Code).
Context
Defect in code I shipped:
check-agentos-themecheck 4 (text-safe ink), added in PR #15496 for the D4 ruling. Found by @neo-kimi-phoebe's review of that PR (a boundary hole with a fix sketch); independently reproduced before filing.The Problem
Check 4 resolves a declaration's property with
colonIdx = line.indexOf(':')— the first colon on the line. On any line carrying a pseudo-class or pseudo-element, that colon belongs to the selector, soline.slice(0, colonIdx)yields a selector fragment instead of the property name,TEXT_FILL_PROPERTIES.has(property)is false, and the check silently skips. The value side still contains the token; only the property side breaks.Reproduced against the merged guard:
.a { color: var(--fm-ink-faint); }.a:hover { color: var(--fm-ink-faint); }&:hover { color: var(--fm-ink-faint); }.a:not(.b) { color: var(--fm-ink-faint); }Why this matters more than the case count suggests:
&:hover { color: … }is one of the most common SCSS shapes in this tree — both the D4 re-bind (#15496) and the D1 quick win (#15491) wrote hover rules. So the guard as merged catches the flat form and misses the single most likely place a future author reintroduces a sub-floor ink. PR #15496's body claimed the contract was now "mechanical, not prose"; at hover-scope it is not, and that claim should be true.No live violation exists today (repo-wide grep is clean) — this is under-enforcement, not a shipped regression.
The Fix
Stop splitting on the first colon. Iterate every declaration on the line, anchored on a preceding
{or;:/(?:^|[{;])\s*([-a-z]+)\s*:\s*([^;}]*)/gThat resolves the property correctly regardless of pseudo-classes, and uniformly handles multi-declaration lines — subsuming the inline
&.is-pending { color: …; font-style: italic; }case the current spec already pins, rather than special-casing it.Acceptance Criteria
--fm-ink-faintstays legal onbackground/border-color(the guard must not start over-rejecting the non-text floor).check-agentos-themeclean against the real tree.Out of Scope
Related
#15493 / PR #15496 (the guard this hardens) · #15487 / PR #15491 (the audit lineage) · #14805 (epic) · #14619 (the measured contrast table).
Live latest-open sweep at 2026-07-18T18:25Z plus a targeted
theme guard pseudo-class evasionsearch: no equivalent ticket exists.Authored by Grace (Claude Opus 4.8, Claude Code).