v2.5.0
The release where the kit stopped taking its own word for anything. Enforcement went from 25 to 37 objective checks, the always-on brief was cut in half, and the two things a gate genuinely cannot do — judge taste, and prove the kit works from a cold start — got real machinery instead of a disclaimer.
Heads-up if you vendored CLAUDE.md
CLAUDE.mdis now ~276 lines, not 576. Depth moved to.claude/rules/(7 files) and loads only when the work calls for it. Headings are unchanged, so pointers into them still resolve, but anyone who vendored the old single-file brief should re-copy:npx ux-ui-agent-skills add claude rules. Always-on and non-negotiable: the emoji ban, the gate protocol, token-by-intent, one-theme, the 8-state table, output completeness.validate_instruction_surface.pyfails the build if one of them is ever demoted, if a rule file is orphaned, or if the brief regrows past its budget.initinstalls a new area (rules), and the package now shipstemplates/,.claude/rules/,.claude/commands/.
Enforcement: 25 -> 37 checks, still all-or-nothing
- Five render-based gates for rules the kit preached and nothing checked:
verify_target_size.mjs(WCAG 2.5.8 with the spec's real spacing / inline / label-hit-area exceptions),verify_reduced_motion.mjs(policy present, motion stopped, and no content lost — catches content only an entrance animation reveals),verify_keyboard.mjs(WCAG 2.1.1, ARIA-aware: rovingtabindexandaria-activedescendantwidgets judged by orphan-widget and dead-arrow signals, not by Tab),lint_intent.mjs(token by intent, measured on the render: a destructive action wearingaction.primaryfails),verify_overflow.mjs(silently clipped text and overlapping controls, with screen-reader-only text correctly exempt).slop_tells.mjsbecame a hard gate. - Each was proven to FAIL on a deliberate violation before it was trusted, and together they found real bugs the previous 25 checks passed: a dead
prefers-reduced-motionrule lost to CSS specificity, a harness with no motion policy at all, and three composite widgets that declared a roving-tabindex model with zero arrow-key handlers. - Responsive is proven against font metrics, not one machine.
verify_responsive.mjs --scalerenders the same narrow widths under a larger root font: the proxy for another platform's wider fallback font (Linux Chromium measured a label 2px wider than macOS Chrome and broke a 280px layout that looked clean locally) and for a user with larger text. Every example now holds at 280px at 1.25x, and that run is a gate. Root causes worth knowing: an<input>keeps an intrinsic ~20-character width that sizes its grid column; a grid item keepsmin-width:autoand can widen its own track; awhite-space:nowraptooltip has no upper bound; a<table>withouttable-layout:fixedis sized by its cells' min-content. - New harness
edge-cases.htmlrenders what production actually contains: unbroken 60-character strings, empty and single-item collections, missing values, ten-digit counts, forty rows.
Judgement, where measurement ends
/critiqueand thedesign-criticsubagent. Its stance is adversarial by design: the work is mediocre until the render proves otherwise, a passing gate is never evidence of taste, and every finding must cite evidence — a file and line, a measured number, or something specific in a specific screenshot. It refuses to review from source alone, screenshots at 1280 and 390 in light and dark, clicks every control, and returns a verdict plus the three reasons a senior designer would send the work back.- The critique was run on the kit's own examples, and it returned
reject. What it found is fixed here, not filed: adata-tableheader that declaredaria-sort, drew a chevron and sorted nothing; a datepicker day drawn as selected that never moved; an Export button styled like a live action with no handler; and an "Email notifications" control that was arole="switch"in name only, identical in both states, ignoring the kit's own Toggle spec. The reference app now leads with one hero metric instead of four equal cards, closes with a real footer instead of empty canvas, carries an elevation scale rather than one flat shadow, and every harness gained display type at--text-4xl(the doctrine's own 2.5x bar) and a closing note.taste_auditandslop_tellsare now clean on every example, in both themes. - Gate 37,
verify_interactive.mjs, exists because of that critique. A control that declaresaria-sort/aria-pressed/aria-expanded/aria-checked/aria-selected, or wears a state-bearing role, must change something on a real click - any attribute, any DOM mutation, or focus landing somewhere other than itself.data-demo-stateopts a deliberate state rendering out, and has to be written by hand so the exemption is on the record. It caught the original bug, two more like it, and then an eval fixture written hours after the critique that named the failure mode. taste_auditmeasures a real character now. Its line-length check assumed1chwas half an em and therefore flaggedmax-width: 65ch- the width the kit itself recommends. It measures the element's own font instead.
Evals: does the kit survive a cold start?
evals/— four cold-start briefs andnode evals/run.mjs <brief-id>, which points thirteen objective gates at what an agent actually produced and prints the brief's requirements for human judgement.--self-testscores the reference app with the same gates and is itself a gate, so the harness cannot rot unnoticed. Building it exposed two real defects the 34-check gate had missed: the reference app overflowed at 280px, andverify_overflowwas flagging screen-reader-only text as silently clipped.
Starting a real product with the kit
templates/product-design/is the recommended Claude Code design-project layout, ready to use: a lean always-on brief,.claude/{rules,skills,commands,settings.json}, a WCAG-verifieddesign-tokens.json(light + dark),src/components/,public/images/,reference/. Scaffold withnpx ux-ui-agent-skills new <dir>or/scaffold-project, andvalidate_template.pykeeps it sound (layout complete, aliases resolve, required contrast pairs pass in both themes).- Scripts take a path, so a product repo can gate its own single-file theme:
validate_tokens.py [file|dir](with explicit paths an unresolved alias FAILS),validate_contrast.py [file],build_tokens.mjs --in <file|dir>. Verified end to end inside a freshly scaffolded repo. - Skills declare how they are invoked —
invocation: user|modelacross all 17SKILL.md, regrouped in this README: six user-invoked skills that orchestrate a job, eleven model-invoked ones that are the discipline the agent applies while it works.