Files
open-design/apps/daemon
YOMXXXandGitHub 3e2f037730 feat(daemon): add CTA hierarchy static QA pass (refs #2251) (#2427)
* feat(daemon): add CTA hierarchy static QA pass

Introduce apps/daemon/src/qa/cta-hierarchy.ts exporting a pure
analyseCtaHierarchy(html) that parses generated prototypes with cheerio
and flags three precision-biased findings: multiple-primary CTAs in the
same section, ambiguous-weight (all CTAs share identical class + inline
style), and misleading-prominence (secondary-coded copy like "Learn
more" / "了解更多" styled with primary weight).

CTA candidates come from <button>, <a>, role="button" with btn/button/cta
class markers plus CTA copy keywords covering both English (Get started,
Sign up, Buy, Subscribe, Learn more, ...) and Chinese (立即购买,
立即下单, 了解更多, ...). Weight is inferred from class tokens
(primary/solid/filled/accent/cta) and from non-transparent inline
background-color, matching the inverse of the issue #2251 sample where
the header CTA was rendered with the neutral .btn style.

This PR only ships the pure function plus its tests. HTTP route, CLI
subcommand, and any auto-repair feedback loop are deliberate follow-ups
so the first cut can land without touching the daemon HTTP surface.

Refs #2251

* fix(qa): respect container boundaries in CTA hierarchy heuristics

Two precision fixes from review of #2427:

- computeContainerKey()'s parent fallback keyed by tag name alone, so
  flat layouts like <div><a class=btn-primary>...</a></div> repeated
  for sibling cards all landed in 'parent:div' and
  detectMultiplePrimary() reported a fake shared-section conflict on
  what is in fact one CTA per card. Switch to parent-node identity
  (positional index of the matched parent within its tag group, same
  trick the landmark branch already uses), so each sibling wrapper
  gets its own bucket.
- detectAmbiguousWeight() compared signatures across the entire
  document, so two unrelated sections each containing one '.btn' CTA
  with matching style would trigger 'ambiguous-weight' despite neither
  container having 2+ CTAs. The PR body's rule is narrower — 'every
  CTA in a container shares the same class + inline style' — so bucket
  by containerKey first and only emit the finding for containers with
  2+ CTAs whose signatures are identical.

Tests lock both behaviors down:
- sibling <div> card-grid without a landmark ancestor stays under the
  multiple-primary threshold;
- one-CTA-per-section pairs stay under the ambiguous-weight threshold.
2026-05-22 16:53:14 +08:00
..