$npx -y skills add bitjaru/styleseed --skill ss-verifyThe VISUAL gate — render a UI or visual artifact through its surface adapter, inspect the actual pixels, then fix and re-render until it passes the composed StyleSeed rule set.
| 1 | # Verify (look at it, don't just read it) |
| 2 | |
| 3 | First resolve the effective rule set from `PRODUCT-PRINCIPLES.md`, `RULESETS.md`, the selected |
| 4 | built-in or reference-compiled grammar, `ADAPTERS.md`, domain/page, optional `PRESETS.md` profile, and |
| 5 | `STYLESEED.md`. Judge pixels against that composed method. A lock value cannot excuse a core |
| 6 | failure, and a profile cannot replace the output grammar. |
| 7 | |
| 8 | `/ss-score` reads the **code** and scores it. But some of the worst "AI-made" tells never appear |
| 9 | in source — they only exist in **pixels**: a hero that doesn't actually dominate, a lower third |
| 10 | of dead whitespace, cramped cards, a web font that silently failed to load and fell back to |
| 11 | Times, two colors that *look* like two accents once rendered, text that's unreadable on its real |
| 12 | background. A human sees these in half a second; a code-reading gate misses all of them. |
| 13 | |
| 14 | `/ss-verify` closes that gap: it **renders the UI, screenshots it, and you look at the image** — |
| 15 | then score the same StyleSeed gate against what you see, fix, and re-render. This is the gate |
| 16 | that most predicts whether a real user will say "this looks designed." |
| 17 | |
| 18 | Run it as the **final** gate after `/ss-score` passes — code-clean is necessary but not |
| 19 | sufficient; pixel-clean is the real bar. |
| 20 | |
| 21 | ## When NOT to use |
| 22 | |
| 23 | - Nothing renderable yet (pure logic/config, or a component with no host page) → use `/ss-score`. |
| 24 | - No way to render at all (no browser, no Playwright, headless blocked) → say so, fall back to |
| 25 | `/ss-score`, and tell the user the visual gate was skipped. **Never claim you verified visually |
| 26 | if you didn't actually see a screenshot.** |
| 27 | - A quick pre-commit pass → `/ss-lint`. `/ss-verify` is heavier (it boots a renderer). |
| 28 | |
| 29 | ## Step 1 — Render it through the active adapter |
| 30 | |
| 31 | For `social-carousel`, `slide-deck`, `document-report`, or `single-frame`, use the companion |
| 32 | renderer and open every required exported frame/page at readable resolution. Verify dimensions, |
| 33 | crop/safe zones, font availability, asset placement, and the export manifest. Do not force a |
| 34 | browser workflow onto a PIL, slide, PDF, or image renderer. |
| 35 | |
| 36 | For `product-ui`, get a real screenshot in priority order: |
| 37 | |
| 38 | **A. Running project (Next / Vite / etc.) — the normal case.** |
| 39 | 1. Start the dev server in the background (`npm run dev` / `pnpm dev` / framework command); wait |
| 40 | for the ready line and capture the port. |
| 41 | 2. Screenshot the route with headless Chromium via Playwright. If the project has `playwright` |
| 42 | in `node_modules`, use it; else use a globally cached Chromium. Minimal script: |
| 43 | ```js |
| 44 | import { chromium } from "playwright"; // or an absolute path into node_modules |
| 45 | const b = await chromium.launch(); |
| 46 | const c = await b.newContext({ viewport: SURFACE, deviceScaleFactor: 2 }); |
| 47 | const p = await c.newPage(); |
| 48 | await p.goto(URL, { waitUntil: "networkidle" }); |
| 49 | await p.evaluate(() => document.fonts.ready); // don't shoot before fonts load |
| 50 | await p.waitForTimeout(400); |
| 51 | await p.screenshot({ path: OUT, fullPage: true }); |
| 52 | await b.close(); |
| 53 | ``` |
| 54 | **Surface viewports:** mobile `{width:390,height:844}` · desktop `{width:1440,height:900}`. |
| 55 | Pick from the lock's `Surface`, or `--surface`. |
| 56 | 3. If a browser MCP (claude-in-chrome) is available instead, navigate + screenshot with that. |
| 57 | |
| 58 | **B. Static HTML file** → open it directly with `file://…` and screenshot (same script). |
| 59 | |
| 60 | **C. Isolated component** (no host page) → render it into a minimal throwaway page that imports |
| 61 | the component with realistic props, then screenshot that. |
| 62 | |
| 63 | **Then actually READ the screenshot back** (Read the PNG). You must *see* it. Shoot at |
| 64 | `deviceScaleFactor: 2` so text is crisp enough to judge. |
| 65 | |
| 66 | ## Step 2 — Score what you SEE (the visual gate) |
| 67 | |
| 68 | Look at the image and run the StyleSeed gate **perceptually**. These are the checks that need |
| 69 | eyes, not source: |
| 70 | |
| 71 | ``` |
| 72 | □ Squint test — blur your focus / imagine it at 50%. Does it still read "AI-generated"? |
| 73 | (bland gradient, pill button + generic sans, icon-chip row, even flat grid) → FAIL |
| 74 | □ Focal — does ONE element actually dominate at a glance? If your eye lands nowhere, |
| 75 | or on an all-even grid, the focal point failed regardless of what code intended |
| 76 | □ Balance — dead whitespace (a lower third of empty), or cramped/colliding elements? |
| 77 | Is the visual weight distributed, or all top-left / all-centered? |
| 78 | □ Fonts loaded — is the intended typeface actually rendering, or a Times/Arial fallback? |
| 79 | (a silent font-load fail is a top "looks cheap" tell — invisible in code) |
| 80 | □ One accent (seen) — count the hues you actually SEE. Two things competing for |