Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-2489Done

The ink scanner reads the WRONG ELEMENT three ways — a descendant variant, a conditional `disabled`, and `placeholder:` text — so its verdict can be about an element that is not the one being painted

Found while running MOTIR-2475 — the faint sweep the scanner from MOTIR-2459 enables. Three sites in the swept tree turned out to be ruled on an element other than the one the ink actually paints, in both directions. The sweep fixed those three instances by restructuring them; the SCANNER still cannot see any of the three shapes, so the guard will not catch the next one.

Repo: motir-core. One PR.

The three shapes, each with the fixture that exposed it

tests/theme/inkContrastScan.ts attributes a class literal to the JSX element whose className attribute lexically contains it (owningElement), then judges THAT element. Three constructs break the assumption that the two are the same element.

#shapewhat the scanner decidedwhat was truefixture (as it stood on 0d5279da)
1a descendant variant[&_svg]:text-(--el-text-faint), [&_.seg-trail]:…judged the ANCESTOR that carries the classthe ink lands only on a descendantPublicTabNav.tsx:75 — a false POSITIVE: ruled violation because the <Link> renders a label, while the ink only ever painted an aria-hidden glyph inside it
2a conditionally disabled control — disabled={disabled || opt.disabled}disabled — the 1.4.3 exemption, unconditionallythe control is disabled only sometimes; the enabled render is unexemptSegmented.tsx:87 — a false NEGATIVE: an inactive segment's trailing COUNT painted at ~2.4:1 and was never reported
3placeholder: ink on a self-closing labelled controldecorative — "a labelled control whose content is glyphs only"rendersText is false for a self-closing element, but a placeholder IS textOnboardingEntrance.tsx:141 — a false NEGATIVE: the entrance textarea's placeholder at 2.61:1

Shape 2 and 3 are the dangerous ones: they make a defect invisible, and the guard's silence then reads as coverage. Shape 1 only costs a spurious failure — but it pushes the next author toward restructuring code to satisfy a parser rather than a reader.

This is about the SCANNER, not the sweep. All three instances are already fixed on main by MOTIR-2475; this card is the class.

Acceptance criteria

  • scanSource judges a descendant-variant class against the descendant it selects, not the element carrying it: a [&_…]:text-(--el-text-faint) whose selector cannot be resolved to an element in the file is reported as unattributable rather than silently attributed to the ancestor — proven by a fixture holding both arms (an aria-hidden glyph target and a text target).
  • A class in a conditionally disabled control (a disabled/aria-disabled attribute whose initializer is an EXPRESSION rather than a literal true) no longer takes the 1.4.3 exemption — the exemption holds only for an unconditionally disabled element, or for the disabled branch of a ternary, which inDisabledBranch already covers. A fixture carries disabled={a || b} and expects violation.
  • A placeholder:-prefixed class is judged as TEXT on every element, self-closing or not — the accessibleName && !rendersText decorative arm must not swallow it. A fixture carries <textarea aria-label placeholder:text-(--el-text-faint) /> and expects violation.
  • Every new rule is proven in tests/theme/inkContrastScan.test.ts on a fixture before the repo-wide run, in the mould the scanner's own header sets out ("a lint whose negative case is never exercised is a lint nobody knows is running").
  • tests/theme/inkContrastLint.test.ts still passes over the whole scanned tree with the widened rules, and any site the widening newly reports is FIXED in this PR, not exempted — the no-allowlist rule from MOTIR-2475 stands.
  • The PR body states the verdict census before and after the widening, so the delta is legible.
  • pnpm lint, pnpm typecheck, pnpm prettier and next build pass.

Context refs

  • tests/theme/inkContrastScan.tsowningElement, isDecorative, isDisabledElement, rendersText: the four functions all three shapes pass through.
  • tests/theme/inkContrastScan.test.ts — the fixture suite to extend.
  • tests/theme/inkContrastLint.test.ts — the repo-wide guard (MOTIR-2475).
  • MOTIR-2477 — the muted sweep, which runs through the SAME three filters and inherits all three blind spots.