feat(lint): add cta-hierarchy-mismatch rule to lintArtifact#2287
feat(lint): add cta-hierarchy-mismatch rule to lintArtifact#2287EthanGuo-coder wants to merge 8 commits into
Conversation
Flags commerce CTAs in <header>/<nav>/<section.hero> whose class list omits the artifact's detected primary button class; emits a single advisory P1 finding per artifact. Closes nexu-io#2251
nettee
left a comment
There was a problem hiding this comment.
I found a few correctness issues in the new CTA hierarchy lint that can produce false positives or unstable counts. Details are inline.
🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.Nested CTAs now count once and transparent backgrounds via rgba/hsla alpha=0 are no longer flagged. Co-Authored-By: Claude <noreply@anthropic.com>
nettee
left a comment
There was a problem hiding this comment.
I found one blocking correctness issue in the primary-class detector and one smaller follow-up in the new alpha parser. Details are inline.
🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.Co-Authored-By: Claude <noreply@anthropic.com>
…cta-hierarchy-mismatch
Parse the full selector prelude so grouped rules like `.btn-primary, .btn-primary:visited { ... }` enter the candidate set, parse `rgba(... / calc(0))` and `calc(0%)` alpha values via `Number.isFinite`, and tighten the rgba/hsla parser to balance nested parens.
|
@nettee Pushed a single follow-up that addresses both round-2 threads and an unrelated CI tsc break I introduced in the prior commit:
128 lint-artifact tests pass (vitest 129/129 including the new rows). The earlier review-summary acknowledgment was premature — both round-2 threads are addressed in |
…nd-color in cta-hierarchy-mismatch
Pick the last declaration in source order whose prop is `background` or `background-color` so `.btn-primary { background: none; background-color: #2f6feb; ... }` is correctly seen as painted.
|
@nettee Fixed in |
…TA class
Skip descendant, child, and pseudo-element branches so `.btn-primary .icon { background: ... }` no longer treats `.btn-primary` as painted; pseudo-classes like `:hover` and type+class compounds are still honored.
|
@nettee Fixed in |
| const out: string[] = []; | ||
| const seen = new Set<string>(); | ||
| for (const branch of prelude.split(',')) { | ||
| const compounds = branch.trim().split(/[\s>+~]+/).filter(Boolean); |
There was a problem hiding this comment.
extractSelectorClassNames() now identifies the last compound with branch.trim().split(/[\s>+~]+/), but that split is not selector-aware. Any valid selector that contains whitespace inside an attribute selector or functional pseudo gets chopped in the middle before we choose last. For example, button.btn-primary[aria-label="Buy now"] { ... } becomes ["button.btn-primary[aria-label="Buy", "now"]"], so .btn-primary never reaches eligible and cta-hierarchy-mismatch silently disappears even though the real primary button class is still present. That breaks the rule this PR is adding on common selectors that annotate buttons with accessible labels or :is(...) branches containing spaces. Please replace the raw split with a right-to-left scan that ignores combinators inside [], (), and quoted strings, and add a regression in apps/daemon/tests/lint-artifact.test.ts for a selector like button.btn-primary[aria-label="Buy now"] { background: ... }.
There was a problem hiding this comment.
@nettee Done — extractSelectorClassNames now uses splitTopLevel(',') and a []/()/quote-aware right-to-left scan, with a regression test for button.btn-primary[aria-label="Buy now"] plus an :is(...) guard in apps/daemon/tests/lint-artifact.test.ts. e03ea6fa2c377c7f9c466f350ab882356396c999
Split selector lists with the existing `splitTopLevel(',')` helper and walk the rightmost compound with a `[]`/`()`/quote-depth scan so attribute-selector values like `button.btn-primary[aria-label="Buy now"]` and `:is(...)` grouping no longer drop the primary class.
Closes #2251
Why
Adds a
cta-hierarchy-mismatchlint rule so commerce CTAs in header/nav/hero regions that use a non-primary button class are flagged as a P1 advisory.What users will see
A new advisory finding
cta-hierarchy-mismatch(severity P1) appears in the artifact lint badge when a<header>,<nav>, or<section class="hero">contains a commerce CTA (Buy / Purchase / Checkout / Order now / Get started / Shop now / Add to cart / Subscribe / Sign up / Start free / Book now / Reserve) whose class list omits the artifact's detected primary button class. P1 surfaces as a warning in the chat UI and does not gate emit — outline CTAs in the header are a legitimate design choice. No change to existing findings, no new exports, no API surface change.Surface area
apps/weborapps/desktop(including Electron menu bar)odsubcommand or flag, newtools-dev/tools-pack/tools-prflag, or newOD_*env var/api/*endpoint, new SSE event, or changed shape inpackages/contractsskills/,design-systems/,design-templates/, orcraft/, or change to the skills protocolTRANSLATIONS.mdfor the locale workflow)package.json(dependenciesordevDependencies); workspace-packagepackage.jsonfiles are out of scope. Include a paragraph on what we get vs. what bytes we ship (seeCONTRIBUTING.md→ Code style)Daemon-internal: one rule block + five private helpers inside the existing
apps/daemon/src/lint-artifact.tsseam (the maintainer-identified home for new lint rules). No new file, no new export, no contract change. The finding rides the existingPOST /api/artifacts/lintresponse.Validation
pnpm --filter @open-design/daemon test -- lint-artifact.test— 109 tests pass (was 96, +13 new forcta-hierarchy-mismatch).pnpm --filter @open-design/daemon typecheck— clean.pnpm guard— clean.