Files
pbakaus_impeccable/tests/fixtures/antipatterns/hero-eyebrow-chip.html
T
e7e923c4ef Skill + craft cleanup, detector hardening, native subagent pipeline (#152)
* skill: drop quality tiers, keep the real brand-craft guardrails

Codex's craft/brand pass introduced fast/ship/showpiece "quality bars"
plus brand-specific build gates, asset ledgers, sub-agent review, and
self-graded fallback labels. In practice those tiers became escape
hatches rather than craft pressure: the final output should always be
10/10, and the real decision points are splashiness and maximalism, not
quality.

Removed:
- All quality-bar / showpiece / fast / ship framing in shape.md and craft.md
- Standalone Brand Direction (#4) and Asset Requirements (#10) sections
  in shape's brief; renumbered back to 1-10
- The Brand hard rules section in brand.md (folded its real prohibitions
  into the existing Imagery and Brand bans sections)
- Brand-specific build-gate item, mock-fidelity bullet, production-bar
  bullet, present-step bullet in craft.md
- Asset ledger ceremony in craft Step 4
- Review-only sub-agents and "self-reviewed fallback, not independently
  validated" machinery in craft.md and polish.md
- The For brand surfaces, assess hard failures subsection in polish.md
  and the brand checklist row
- tests/brand-showpiece-reference.test.mjs (and its package.json wiring)

Kept (the real nuggets):
- Asset-substitution prohibition: image-led briefs ship real/generated
  assets or canvas/SVG/WebGL, not generic CSS panels, cards, bullets,
  or copy
- Repeated tiny uppercase tracked kicker labels as a brand ban
- Detector/QA output is defect evidence only, never proof of quality
- "What visual assets are real content here?" discovery question
- Inspect each major section individually for brand and long-form work
- repeated-section-kickers detection rule + fixture
- CLI improvements (JSON to stdout, -json/-fast aliases, severity field)
- critique.md: npx impeccable detect --json fix

Harness output dirs refreshed via bun run build. Full test suite (186)
passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* skill: strip gate ceremony; require shape pause; allow compact briefs

The setup gate table and IMPECCABLE_PREFLIGHT banner pushed every
craft run through ritual restatement (PRODUCT.md → original prompt
→ round 1 → round 2 → 70-line "confirmed brief" → critique → summary,
all saying the same thing). Replaced with imperative prose that still
demands the same work but skips the user-facing telemetry.

Specifically:

SKILL.md
- Drop the Setup gate table and IMPECCABLE_PREFLIGHT banner.
- Keep the imperative steps explicitly: load context, identify register
  and load brand.md or product.md, AND load the matching command
  reference (craft.md / shape.md / etc.) when a sub-command is invoked.
  The command-reference step is non-negotiable; without craft.md loaded
  the agent skips the shape-and-confirm pause.

craft.md
- Drop the Build Gate / Craft Contract formal sections; replace with
  one paragraph stating prerequisites.
- Step 1 explicitly requires ending the response after presenting the
  shape output; the user must confirm before any code lands. Allows a
  compact 3-5 bullet brief when the prompt + PRODUCT.md already pin
  direction (full 10-section structure reserved for genuinely
  ambiguous tasks).
- Step 3 image gate skips silently when image generation isn't
  natively available; no user-facing announcement.
- Step 6 explicitly legitimizes "first pass clean, shipping" as a
  valid endpoint and bans inventing fake defects to demonstrate
  iteration.

shape.md
- Cap discovery at 1 round by default; second round only when first
  leaves material gaps.
- Adds an "assert-then-confirm, not menu-with-escape" rule: when
  PRODUCT.md and the prompt make one option obvious, name it and ask
  for confirm or override instead of enumerating "Restrained /
  Committed / Or something else?" as a real choice.
- Phase 2 brief has two forms now: compact (default for clear briefs)
  and full structured (genuinely ambiguous). Open Questions can't
  double as leading-with-Recommend; if you'd write "Recommend: X",
  decide X.
- Image gate same as craft.md.

Validated end-to-end with a Haiku skill-on observability run: agent
loads craft.md plus the brief's recommended implementation refs,
pauses for one productive question (accent color, trace fidelity,
CTA), and ships an artifact with zero side-tab violations vs. the
original v1 baseline. Cost trades up modestly for that quality.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* craft.md Step 6: Reading the screenshot is the inspection, not taking it

A v4 eval run took 4 targeted screenshots (hero, mobile, tablet,
query-section) and then never Read any of them back. The agent treated
browser_screenshot itself as "I inspected" and shipped without the
multimodal feedback loop ever closing. Detector caught the resulting
slop (5+ side-tab violations) on adjacent runs that did the same thing.

Step 6 now spells out the pattern explicitly: take the screenshot,
then Read the resulting PNG so its image content enters the
conversation as multimodal input, then critique what you actually see
in the image. With a check: "if your critique could have been written
without looking at the image, you didn't look at the image."

Validated with v5b: agent took 6 screenshots, Read all 6 back, and
shipped with zero detector findings (vs the previous greenfield runs
that hit 1-12 findings each).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* craft + brand: framework foundation, build-pipeline respect, image verification

Three closely-linked additions surfaced by an eval-harness session
investigating why the agent always shipped flat single-file HTML and
zero imagery on greenfield brand briefs.

1. craft.md gains a new Step 0 "Project Foundation" before Shape.
   Detects existing framework / component library / icon set and
   uses what's there. Greenfield: ask the user via AskUserQuestion
   with sensible defaults framed by the brief (Astro for content/
   brand sites, SvelteKit/Next/Nuxt for app surfaces, single
   index.html only for one-shot demos). Skipping the framework
   decision and writing flat HTML "to satisfy the spec" produces
   work that reads as a 2018 prototype regardless of visual
   quality.

2. craft.md Step 5 production bar gains two bullets:
   - Respect the build pipeline. Edit source files and run the
     project's `npm run build`; do not write to build/ / dist/ /
     .next/ directly with cat/heredoc/Bash redirects. Bypassing
     the pipeline skips asset hashing, image optimization, code
     splitting, and CSS extraction.
   - Verify external image URLs before referencing them. Use an
     image-search MCP, web-fetch tool, or browser if available;
     guessed photo IDs ship as broken-image placeholders.

3. brand.md "Imagery" section:
   - Generalizes the Unsplash URL guidance to "verify URLs
     before referencing them" with a hierarchy: image-search MCP
     > web-fetch > confidence-restricted manual selection >
     fewer photos.
   - Tightens the tech/dev-tool exception. Old line "zero imagery
     can be correct" gave models a permission slip. New framing
     keeps the underlying truth (typography + code + diagrams
     primarily carry voice) but raises the floor: imagery still
     earns its place when it serves the brief, and skipping it
     requires naming the typographic/diagrammatic move that's
     carrying the visual weight instead. "Zero imagery is the
     failure mode of laziness, not restraint."

Eval-harness corpus that prompted this: 19/19 brand landing tasks
shipped 0 images each, including ones where Opus had taste enough
to break the dev-tool color default lane. The skill needs to teach
both halves of the decision; the harness shouldn't have to nudge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* detector: body-text-viewport-edge rule + OKLCH/var-resolution + anchor-inherit FP fixes

New rule: body-text-viewport-edge flags body paragraphs that render flush
against the left/right viewport edges (no container padding). Tested via
the new tests/fixtures/antipatterns/body-text-viewport-edge.html fixture
(3 flag cases, 5 pass cases) and the test in detect-antipatterns-browser.

False-positive class fixes — all jsdom-mode only (real browsers resolve
the cascade correctly so these gates stay inert there). Five related
gaps that compounded into ~14× spurious contrast findings on Tailwind v4
pages with OKLCH color tokens:

  • OKLCH parser. jsdom returns the literal "oklch(...)" string from
    getComputedStyle; the detector now converts to sRGB via Björn
    Ottosson's matrices. Handles Tailwind v4's compact minified form
    "oklch(21.5%.02 50)" (no space after %).
  • var() resolution. resolveBackground + checkElementColors now
    accept the existing customPropMap and parse `var(--color-paper)`
    etc. as proper RGB via the new parseColorResolved helper.
  • bg-color before bg-image. The old order bailed on any gradient
    ancestor before checking for a solid background-color underneath,
    causing the body's decorative paper-grain gradient to be measured
    against instead of the page's actual `bg-paper` cream.
  • body/html-level gradient → white fallback. When the only opaque
    ancestor we can read is body/html with a gradient overlay (and
    jsdom can't decompose `background: var(--paper) gradient` to
    extract the solid color), return white instead of falling through
    to resolveGradientStops — which was picking up paper-grain noise
    colors and using them as the bg.
  • Anchor-inherit workaround for jsdom :link UA specificity.
    Tailwind v4's preflight declares `a { color: inherit }` (0,0,1).
    jsdom's UA stylesheet has `:link { color: blue }` at (0,1,1) and
    wins the cascade. Real Chrome wraps :link in :where() (0,0,0) so
    the page rule wins. When the page declares the inherit rule AND
    we see jsdom's default `rgb(0,0,238)` on an anchor, walk to the
    nearest non-anchor ancestor and use its color.
  • Alpha-fallback safety gate. When text has alpha<1 AND we couldn't
    find an opaque ancestor (effectiveBg null), skip the contrast
    finding. Covers any remaining FP class the deeper fixes miss.

Verified end-to-end against an Opus iter-1 artifact on Tailwind v4 with
14 cream/cream FPs + 2 blue-link UA FPs before; 0 findings after, while
the color.html fixture's 12 real low-contrast cases continue to flag
(verified via direct detectHtml calls).

cli/engine/detect-antipatterns-browser.js is the generated browser
distribution — regenerated from .mjs via scripts/build-browser-detector.js
(no manual edits to the generated file).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* craft.md: tighten verbose passages, de-codex Step 6, cut redundancies

Cumulative reduction: 218 → 155 lines (-29%).

Step 0: drop the "Why this matters" paragraph at the end. The body of
Step 0 already makes the framework-pick point; the paragraph just
re-explains it with extra rhetoric.

Step 1: replace the 4-sentence "you must end your response" block with a
single line. The original said the same thing three different ways.

Step 3: trim the conditional / defensive scaffolding (Purpose subsection,
"do not skip because the eventual UI is semantic..." paragraph,
duplicated approval-loop guidance). Mock fidelity inventory preserved.

Step 4: drop the "keep UI text semantic" sentence; it duplicates Step 5's
"Semantic first" rule. The rasterized-vs-semantic decision rule stays.

Step 5: tighten each production-bar bullet to bold-lead + specifics
format. All 15 rules preserved (real content, mock ingredients, semantic
first, spacing/alignment, typography, state coverage, interaction quality,
icon set, build pipeline, image URL verification, optimized imagery,
premium motion, maintainability, technical cleanliness, ask-when-uncertain).

Step 6: rewrite around "look at what you built like a designer would —
your eyes are whatever the harness gives you." Drops Codex-specific
"In Codex, use browser-use" bias. Drops the verbose 3-step Read pattern
(condensed to one sentence). Drops the 1-8 numbered checklist (replaced
by a tight paragraph). Keeps the load-bearing rules: read the PNG,
don't fabricate iteration, mock fidelity reference, exit bar = studio
defensibility.

Step 7: drop the closing "Iterate based on feedback. Good design is
rarely right on the first pass" preachy filler.

All em-dashes converted to semicolons / colons / periods to satisfy
the skill prose validator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* build: native subagent pipeline + Codex-only asset producer

Adds an agent cross-compile pipeline alongside the existing skill
pipeline. Sources live at skill/agents/*.md; providers that declare
agentFormat (codex-toml, claude-md) emit native subagent files. An
optional providers: <list> field on an agent gates which harnesses
get a copy; default (no field) ships everywhere.

The impeccable-asset-producer agent is opt-in to Codex only. It's
useful for Codex's native image generation path and is untested
elsewhere; Claude has no native image gen anyway.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* brand: inverse-test + cultural-symbol palette guardrail

Two additions to the brand register reference:

- Inverse slop test: describe the page the way a competitor would
  describe theirs. If that sentence fits the modal landing page in
  the category, restart.
- Palette guardrail: when a cultural-symbol palette is the obvious
  pull, reach past it. Let cultural reading come from typography,
  imagery, and copy.

Harness mirrors regenerated; some also catch up to the image-
verification paragraph from e3ad2ef that hadn't been re-synced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* PRODUCT.md: widen audience beyond developers

Designers, product managers, and engineers all use AI coding tools
and want better design output. Keeping the audience narrow to
"frontend and full-stack developers" understates who the skill is
actually for. Also retitles "developer" to "user/builder" in the
purpose statement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* site + build: bump rule count to 29, strip changelog from detector check

Two changes:
- site/pages/index.astro: three live mentions of "28 rules / checks"
  bumped to 29 after the body-text-viewport-edge rule landed in
  b9bf496.
- scripts/build.js: the detection-count validator was reading the
  unstripped content, so historical counts inside changelog entries
  (e.g. "28 rules" from an older release note) were flagging against
  the current detector total. The command-count check already strips
  the changelog ul; the detection check now does the same.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: align hero-eyebrow-chip fixture with relaxed rule gates

b9bf496 intentionally relaxed two gates in checkHeroEyebrow:
- removed the heading-size ≥ 48px anchor (modern hero h1s use
  clamp/vw/var that jsdom can't resolve)
- raised the eyebrow text ceiling from 30 to 60 chars

Two fixture cases that satisfied the negative side of the old gates
now match the rule:

- "Body-Sized Heading Below Eyebrow" — 24px h1 with tracked-caps
  label above. Per the rule's stated intent ("a tiny tan label
  directly above any h1 is the antipattern regardless of how big
  the h1 ends up"), this is a flag.
- "Long Uppercase Sentence Above Hero" — 46-char tracked-caps label
  is under the new 60-char ceiling, so still eyebrow-shaped.

Both cases moved from the should-pass column to should-flag, with
case descriptions rewritten to explain the gate they exercise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Paul Bakaus <paulbakaus@pauls-mbp-3.lan>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:11:18 -07:00

197 lines
6.9 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hero-Eyebrow-Chip — Should Flag vs Should Pass</title>
<style>
/* ─── Layout convention: two-column fixture ─────────────────────────
Left column = patterns the rule SHOULD flag.
Right column = legitimate patterns the rule should NOT flag.
Each test case is annotated with a unique <h1> text so the test
can match snippets back to expectations.
──────────────────────────────────────────────────────────────── */
body { font-family: system-ui, sans-serif; margin: 0; padding: 24px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1200px; margin: 0 auto; }
.col h2.col-label { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }
.case { margin-bottom: 32px; padding: 24px; border: 1px solid #e2e8f0; border-radius: 12px; background: #fff; }
.case p { font-size: 14px; margin: 12px 0 0; color: #64748b; }
.case h1 { margin: 0; font-family: 'Inter', sans-serif; line-height: 1.05; }
/* Hero size used by most cases */
.hero { font-size: 88px; font-weight: 600; }
/* ── FLAG cases ── */
.eyebrow-classic {
text-transform: uppercase;
letter-spacing: 0.15em;
font-size: 12px;
color: #64748b;
font-weight: 600;
margin: 0 0 16px;
}
.eyebrow-span {
display: inline-block;
text-transform: uppercase;
letter-spacing: 0.16em;
font-size: 11px;
color: #475569;
font-weight: 600;
margin: 0 0 16px;
}
.eyebrow-pill {
display: inline-block;
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 12px;
background: #f1f5f9;
color: #334155;
border-radius: 999px;
padding: 4px 12px;
font-weight: 600;
margin: 0 0 16px;
}
.eyebrow-already-uppercase {
letter-spacing: 0.18em;
font-size: 11px;
color: #b45309;
font-weight: 700;
margin: 0 0 16px;
}
/* ── PASS cases ── */
.pass-eyebrow-no-tracking {
text-transform: uppercase;
letter-spacing: normal;
font-size: 12px;
color: #64748b;
font-weight: 600;
margin: 0 0 16px;
}
.pass-body-heading {
font-size: 24px;
font-weight: 600;
font-family: 'Inter', sans-serif;
margin: 0;
}
.pass-card-caption {
text-transform: uppercase;
letter-spacing: 0.18em;
font-size: 11px;
color: #94a3b8;
font-weight: 600;
margin: 0 0 8px;
}
.pass-bare-hero {
font-size: 64px;
}
.pass-h2-above-h1 {
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.18em;
font-weight: 700;
color: #475569;
margin: 0 0 16px;
font-family: 'Inter', sans-serif;
}
.pass-long-uppercase {
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 12px;
color: #475569;
font-weight: 600;
margin: 0 0 16px;
}
</style>
</head>
<body>
<div class="grid">
<!-- ════════════════════════════════════════════════════════════════
LEFT COLUMN: should flag
═══════════════════════════════════════════════════════════════ -->
<div class="col" data-col="flag">
<h2 class="col-label">Should flag</h2>
<div class="case">
<div class="eyebrow-classic">AI-NATIVE WORKFLOWS</div>
<h1 class="hero">Eyebrow Above Hero</h1>
<p>Classic uppercase tracked div eyebrow above an 88px hero.</p>
</div>
<div class="case">
<span class="eyebrow-span">NEW IN 2026</span>
<h1 class="hero">Span Eyebrow Above Hero</h1>
<p>Span variant with the same uppercase + tracking + small-size shape.</p>
</div>
<div class="case">
<span class="eyebrow-pill">FEATURED</span>
<h1 class="hero">Pill Chip Above Hero</h1>
<p>Pill-shaped chip with background, border-radius, padding.</p>
</div>
<div class="case">
<span class="eyebrow-already-uppercase">NEW</span>
<h1 class="hero">Already Uppercase Text</h1>
<p>Text typed uppercase, no text-transform, but matching tracking and size.</p>
</div>
<div class="case">
<div class="eyebrow-classic">SECTION KICKER</div>
<h1 class="pass-body-heading">Body-Sized Heading Below Eyebrow</h1>
<p>Eyebrow above a 24px h1. Modern hero h1s use clamp() / vw / var() that jsdom can't resolve, so the rule no longer gates on heading font size — a tracked-caps label above any h1 is the antipattern shape.</p>
</div>
<div class="case">
<div class="pass-long-uppercase">A VERY LONG UPPERCASE TABLE OF CONTENTS HEADER</div>
<h1 class="hero">Long Uppercase Sentence Above Hero</h1>
<p>46-char uppercase tracked label — under the 60-char eyebrow ceiling, so it still reads as an oversized eyebrow.</p>
</div>
</div>
<!-- ════════════════════════════════════════════════════════════════
RIGHT COLUMN: should pass
═══════════════════════════════════════════════════════════════ -->
<div class="col" data-col="pass">
<h2 class="col-label">Should pass</h2>
<div class="case">
<div class="pass-eyebrow-no-tracking">UPPERCASE LABEL</div>
<h1 class="hero">Eyebrow With Normal Tracking</h1>
<p>Uppercase label above a hero but with letter-spacing: normal.</p>
</div>
<div class="case">
<div class="pass-card-caption">CARD CAPTION</div>
<p>Plain body text below the caption.</p>
<h1 class="hero" style="margin-top:32px">Uppercase Caption Far From Hero</h1>
<p>Hero exists, but its preceding sibling is a paragraph, not the eyebrow.</p>
</div>
<div class="case">
<h1 class="pass-bare-hero">Hero With No Eyebrow</h1>
<p>Bare h1 at 64px, nothing above it.</p>
</div>
<div class="case">
<h2 class="pass-h2-above-h1">SECTION HEADING</h2>
<h1 class="hero">Heading Above Heading</h1>
<p>An h2 styled like an eyebrow above an h1 — heading-tag exclusion must skip this.</p>
</div>
</div>
</div>
</body>
</html>