The ink scanner walks PAST an element's own white fill — `--el-page-bg` and `--el-card` are the same colour and only one of them stops the surface walk
Found while running MOTIR-2496 — one of the five sites that card cleared was a FALSE POSITIVE, and the ink moved there for reasons of its own. The misattribution is the class, and it is still in the scanner.
Repo: motir-core. One PR.
The measurement
nearestSurface in tests/theme/inkContrastScan.ts walks ancestors looking for the background an ink pairs with. It has exactly one early return for a SAFE surface:
if (/\bbg-\(--el-card\)/.test(blob)) return { className: blob, tinted: false };
const tinted = TINTED_SURFACE_CLASSES.find((surface) => blob.includes(surface));
if (tinted) return { className: tinted, tinted: true };
--el-page-bg is not in that check, and it is not in TINTED_SURFACE_CLASSES either — so the walk sails straight through an element that paints its own white background and keeps climbing until it finds a tinted ancestor.
Both tokens resolve to the same value:
--el-page-bg: var(--color-background);
--el-card: var(--color-background); /* untinted card surface */
So two elements painted identical white get opposite verdicts depending on which token the author reached for.
The instance
app/(authed)/backlog/_components/CreateIssueRow.tsx — the inline create-issue input carries bg-(--el-page-bg) on ITSELF, inside a row whose ancestor is --el-surface-soft. Its placeholder therefore paints on white (muted = 4.54:1, passing), and the guard reported it as 4.12–4.34:1 on bg-(--el-surface-soft).
MOTIR-2496 moved that placeholder to --el-text-secondary anyway — 4.54:1 is 0.04 of headroom on placeholder text, and its three sibling composers were moving in the same change — so the lane is green. The scanner is still wrong, and the next bg-(--el-page-bg) element inside a tinted container gets the same false verdict with no record of why.
Why it matters more than one false positive
A false POSITIVE in this guard is not free, and MOTIR-2489's own card said so: "it pushes the next author toward restructuring code to satisfy a parser rather than a reader." Here the cheapest way to silence it is to swap bg-(--el-page-bg) for bg-(--el-card) — zero pixels changed, guard satisfied, and the codebase now carries a token choice made for a parser. That is the outcome to prevent.
Acceptance criteria
nearestSurfacetreats an element's ownbg-(--el-page-bg)as a TERMINATING SAFE surface, exactly asbg-(--el-card)already is — proven by a fixture intests/theme/inkContrastScan.test.tsholding both arms: a muted ink on an element carryingbg-(--el-page-bg)INSIDE abg-(--el-surface-soft)ancestor expects NO violation, and the same ink with no own-background expects one.- The safe-surface set is derived from, or asserted against, the fact that both tokens map to
--color-background— a new white alias must not be able to reopen this silently. A test that readstheme.cssand fails when a--el-*background maps to--color-backgroundwithout appearing in the safe set is the shape to aim for. tests/theme/inkContrastLint.test.tsstill passes over the whole scanned tree, and the PR states the verdict census before and after so any site the correction newly SILENCES is visible — a fix that quietly drops real findings is the failure mode here.- No site is exempted or allowlisted to make this pass; the change is to the resolver.
pnpm lint,pnpm typecheck,pnpm prettierandnext buildpass.
Context refs
tests/theme/inkContrastScan.ts—nearestSurfaceandTINTED_SURFACE_CLASSES.tests/theme/inkContrastScan.test.ts— the fixture suite to extend.packages/design-system/theme.css— the--el-page-bg/--el-carddefinitions, lines ~2240 and ~2455.app/(authed)/backlog/_components/CreateIssueRow.tsx— the instance, and the comment MOTIR-2496 left on it naming this card.- MOTIR-2489 — the prior wrong-element card; same family, different function.