| name | design-craft-rubric |
| description | JUDGE whether an already-rendered UI surface is good enough to ship — quality evaluation of something that EXISTS, not creation of something new. GLM-5.2 has no vision, so judgment runs on Playwright-MCP computed-style probes (getComputedStyle JSON: fontSize / color / padding / gap / contrast), not on eyeballing pixels. Scores 7 dimensions PASS/FAIL (visual hierarchy / spacing-rhythm / alignment / contrast+WCAG / density / design-token conformance / DESIGN.md-tone) + runs a 13-tell "AI-slop" gate (warm-cream hero, near-black+neon, Inter-only, white+purple, uniform sizing, vague headlines, scattered motion…). ROUTE BY INTENT, NOT KEYWORDS — load this whenever the intent is to evaluate quality of a built surface, including indirect signals: a shared screenshot the user reacts to with dissatisfaction, hesitation, or silence; "이거 이상하지 않아?", "괜찮아?", "어때"; a ship/no-ship decision moment ("출시해도 돼?", "이대로 갈까?"); any qualitative complaint about a rendered screen ("AI스러워", "올드해", "트렌디하지 않아", "이탈하고 싶어져"); explicit review/audit/craft-check/WCAG requests. Returns per-dimension PASS/FAIL + prioritized fix-list + PROCEED/REVISE verdict, never a rolled-up score. NOT for designing something new (that is ux-design-baseline) and NOT for a repeated improvement loop (that is ui-iterate). |
Design craft rubric — GLM-5.2 edition (blind-model, probe-driven)
The judging knowledge for any rendered UI surface. GLM-5.2 has no vision (config-confirmed: modalities.input: ["text"] only) — so this rubric does NOT rely on the model "looking at" a screenshot. It judges through Playwright-MCP computed-style probes that return text the model can reason over. A screenshot is captured only as an audit artifact for a human (or vision-capable) reviewer, never as GLM-5.2's own verification input.
The core constraint and how this rubric compensates
| GLM-5.2 reality | Consequence | Compensation in this rubric |
|---|
| Text-only input | Cannot self-interpret a screenshot | browser_evaluate returns computed styles as JSON → judge those |
| 1M-token context | Can hold full token system + CSS + probe transcript at once | No need to split across sub-agents; load everything in main context |
| Strong coding + tool use | Excels at probe scripts and rule application | The rubric is rule-based (PASS/FAIL with concrete bars), not vibes |
The blind loop: render → DOM probe (text) → computed-style probe (text) → rubric (rules). The model never needs to "see" — it reads structured probe output and applies concrete pass-bars.
Fail-fast: environment ≠ craft defect (apply BEFORE the rubric)
The trigger is the server being UNREACHABLE at the TRANSPORT layer — dev server won't start, or a base-URL probe gives connection-refused / timeout (no HTTP response). An HTTP 4xx/5xx means the server is UP — that is a render-blocker to flag per rubric step 3 (or 확인 불가), NOT environment.
If transport-unreachable: STOP, report 환경 문제 — 서버 미기동 (transport-unreachable), 환경 선결 in one line and end. Do NOT retry / re-screenshot / long static-fallback. Cold next dev / vite compile is not down — browser_wait_for an anchor first; only a refused transport after that is environment.
Probe procedure (the blind model's "eyes" — run BEFORE judging)
Every judgment must be backed by a probe result, never inferred from reading the source CSS. The order matters: text-readable structure first, then computed values.
- Identify the surface. From
git diff --name-only (or the user's request), identify the changed directory and its dev URL + route. Each repo maps its own dir → port; check the repo's package.json / dev script. If a repo has multiple frontends, identify which one.
- Start the dev server if not running;
browser_wait_for a stable anchor (cold compiles on demand — do not capture mid-compile).
browser_navigate the route. browser_resize to 1440×900 (desktop) AND 390×812 (mobile) — both, every time. Single-viewport judgment misses responsive failures.
browser_snapshot — the accessibility tree. This is the text representation of layout/roles/labels and is the primary structural input for a text-only model (more useful than a screenshot). Verify: every interactive element has a role + accessible name; heading order is hierarchical; landmarks present; no vague labels.
browser_evaluate — the computed-style probe. Run a function that returns a JSON report (template below). This is the blind model's substitute for "looking."
browser_console_messages (level: warning) — catch font-load 404s, CSS parse errors, specificity overrides. A 4xx/5xx on a real call is a render-blocker.
- Load the owning DESIGN.md for the token scale + tone before scoring. If the repo has no DESIGN.md → honest-degrade per dim 6 (N/0 + heuristic flags), do NOT block.
The computed-style probe template (browser_evaluate)
Adapt selectors to the surface. This returns text the model can judge directly — no pixel interpretation needed.
() => {
const probe = (sel) => {
const el = document.querySelector(sel);
if (!el) return null;
const cs = getComputedStyle(el);
const rect = el.getBoundingClientRect();
return {
tag: el.tagName.toLowerCase(),
text: (el.textContent || '').slice(0, 60),
fontSize: cs.fontSize, fontWeight: cs.fontWeight,
fontFamily: cs.fontFamily.split(',')[0].trim(),
color: cs.color, backgroundColor: cs.backgroundColor,
padding: cs.padding, margin: cs.margin,
borderRadius: cs.borderRadius, gap: cs.gap,
rect: { w: Math.round(rect.width), h: Math.round(rect.height) },
};
};
const sels = ['h1', 'h2', 'button', '[class*="primary"]', '[class*="card"]'];
const contrast = (fg, bg) => { };
return {
viewport: { w: innerWidth, h: innerHeight },
elements: sels.map(s => ({ selector: s, ...probe(s) })),
fontLoaded: { display: document.fonts.check('16px "YourDisplayFont"'),
body: document.fonts.check('16px "YourBodyFont"') },
reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
transitionCount: document.querySelectorAll('[class*="fade"],[class*="anim"]').length,
};
}
The probe converts "is this good?" into "do these values satisfy the rubric?" — a question a text model can answer reliably.
The 7-dimension rubric (each = PASS or FAIL with a concrete bar; NO aggregate score)
Every dimension is PASS or FAIL with a concrete pass-bar; on FAIL emit the specific fix (surface/element + what is wrong + the target value). NO vibes, NO single aggregate score, NO "8/10." A rolled-up number is not actionable; per-dimension P/F + a fix list is.
| # | Dimension | PASS-bar (concrete, probe-checkable) | On FAIL — the fix |
|---|
| 1 | Visual hierarchy | The primary action is the single most prominent element above the fold (largest/highest-weight/strongest color). Heading→body type contrast ≥ 1.25× size step OR a clear weight step. Probe: fontWeight of primary CTA > siblings; fontSize step ≥ 1.25×. | Name the element that wrongly out-competes the primary action; give the target (e.g. "demote 보조 button to variant=ghost; primary stays solid --color-primary"). |
| 2 | Spacing / rhythm | Gaps come from ONE spacing scale (e.g. 4/8/12/16/24/32). Vertical rhythm between sibling blocks is consistent (same gap token repeated). Probe: enumerate gap/margin values → all snap to the scale. | List the off-scale gaps with rendered px (probe.margin/probe.gap) + the nearest scale token to snap to (e.g. "13px → 12px (space-3)"). |
| 3 | Alignment / grid | Elements share edges / a grid; labels and fields, card rows, action clusters align to a common left/right edge. Probe: getBoundingClientRect().x of aligned elements equal within 1px. | Name the misaligned elements + the shared edge they should snap to (target px / grid column). |
| 4 | Contrast + WCAG AA | No low-contrast text. Body text ≥ 4.5:1, large text ≥ 3:1. Probe: parse computed color/backgroundColor RGB → compute ratio. (If the repo runs an axe CI gate, defer the formal verdict there; do NOT re-run axe and self-report.) | Flag the low-contrast pair with rendered fg/bg hex + measured ratio (e.g. "3.2:1, AA 4.5:1 미달 → darken fg to --text-strong"). |
| 5 | Density vs whitespace | Content breathes — no cramped wall of equal-weight controls. Related controls grouped with whitespace. Probe: count siblings in one cluster; flag clusters of >6 equal-weight controls with no grouping. | Count the competing controls; recommend grouping/collapsing + the whitespace token (e.g. "group 8 filters into a Toolbar w/ space-6 between groups"). |
| 6 | Design-token conformance | Rendered values match the owning repo's OWN token scale — color/space/radius/type from that repo's DESIGN.md + token CSS. Each repo owns its tokens; there is NO canonical cross-repo scale and you NEVER judge surface A against repo B's tokens. A finding = a rendered value NOT on the scale: ad-hoc text-[Npx], raw hex (#1a1a1a), arbitrary gap-[13px]. If no named scale → honest-degrade: "token conformance = 확인 불가 (N/0)" + HEURISTIC flags (grep / probe for raw hex + text-[Npx] + *-[…px]). Do NOT block on a missing scale. | List each ad-hoc value (selector + rendered) + the named token it should map to within that repo. |
| 7 | DESIGN.md tone adherence | The rendered surface matches the owning repo's DESIGN.md tone. Color temperature, radius, density, voice match — never cross-applied across repos. If the repo has no DESIGN.md → "tone = 확인 불가 (DESIGN.md 부재)"; do NOT invent a tone. | Quote the DESIGN.md line violated + the rendered mismatch (e.g. "DESIGN.md L## says warm cream #FDFBF7; rendered bg is cold #FFFFFF"). |
Concrete values only — "대비비 3.2:1 (AA 4.5:1 미달)" not "대비 낮아 보임". Hedge BANNED. Layout reachability (scroll / viewport / button reach) is a CORE requirement, never deferred as "위험."
The 13 AI-slop tells (apply as a hard-fail gate AFTER the rubric)
GLM-5.2 — like every LLM — defaults to the most probable token. That statistical pull reasserts on every generation unless explicitly overridden. These 13 tells are the concrete shape of "generic AI design." Run them as a checklist: present/absent + evidence (probe value). Any "present" → REVISE.
| # | Tell | Why it reads as AI | Concrete fix |
|---|
| 1 | Warm cream hero (~#F4F1EA) + high-contrast serif + terracotta accent | Default cluster #1 (Anthropic-named) | Derive palette from the brief's actual subject/materials → 4–6 named hex tied to content |
| 2 | Near-black bg + single acid-green / vermilion accent | "Dark + neon" shortcut | ≥2 non-neutral color roles; neon reserved for one signal |
| 3 | Broadsheet pastiche: hairline rules, border-radius:0, dense newspaper columns | "I want to look serious" costume | Use only when content is genuinely editorial; otherwise vary radius intentionally |
| 4 | "Big number + small label + supporting stats + gradient accent" hero | THE template hero answer | Open with the subject's most characteristic artifact (live demo, real screenshot, interactive moment) |
| 5 | Inter (or system sans) as the only typeface | "The Inter Default" — ships everywhere | ≥2 type roles: characterful display (with restraint) + complementary body (+ optional mono for data) |
| 6 | White + purple / purple-to-blue gradient | Most common LLM color default | Semantic color via CSS custom properties (--color-action-primary), never --color-gradient-start |
| 7 | Uniform component sizing: same radius/padding/height everywhere | Flat, no hierarchy | Vary radius/padding/scale intentionally to encode hierarchy |
| 8 | 01 / 02 / 03 numbered markers | Decorative numbering encoding nothing | Use only for an actual ordered sequence/process/timeline; otherwise cut |
| 9 | Scattered fade-in / hover-that-does-nothing / button snap | "Extra animation = AI feeling" | One orchestrated motion moment > many. Motion must do exactly one of: state change / direct attention / reinforce brand |
| 10 | Vague aspirational headline ("Build the future of work") | Averaged from training data | Be specific (Stripe → "Financial infrastructure to expand access to the global economy") |
| 11 | Hedging / superlative copy ("may help", "best-in-class") | Statistically safe, commitment-free | Active voice; control label = outcome ("Save" not "Submit"); error explains + how to fix, no apology |
| 12 | Stock/AI imagery: diverse-group-at-laptop, 3D blobs, plastic illustrations | Placeholder tells | Real screenshots, real photos, or custom illustrations tied to brand |
| 13 | Over-designing (parallax + custom cursor + animated bg + layered gradients) | Noise mistaken for distinctiveness | Restraint: one signature element, everything around it quiet (Chanel rule: remove one accessory before shipping) |
Self-uniqueness test (mandatory pre-build gate, not just post-build): re-prompt yourself generically — "design a [category] page" — and if you arrive at the same design, that part is a default. Revise it and state what you changed and why. Only write code after the plan passes this test.
Output contract (per-dimension PASS/FAIL + slop gate + prioritized fix-list + verdict)
- BLUF header —
결론 (3줄): 렌더 craft P/F 요약 / 정적 준수 (DESIGN.md 톤 일치 / WCAG) / 평결 (PROCEED | REVISE) + 다음 액션.
- Sections: ① DESIGN.md/FLOWMAP 준수 (정적, file:line; DESIGN.md 부재시 "확인 불가") ② 접근성 (WCAG, guideline# + 측정값) ③ 레이아웃 도달성 ④ 렌더 craft 표 (7행 전수 — skip BANNED) ⑤ AI-slop 게이트 (13 tells present/absent + evidence) ⑥ 우선순위 FIX 리스트 (
surface/element · 무엇이 잘못 · target value, 끝에 "보류 / 현 디자인 유지" option; 막연한 a/b 메뉴 BANNED) ⑦ 평결.
FAIL 0건 + slop 0건 = PROCEED. FAIL ≥1 OR slop ≥1 = REVISE + 위 FIX 리스트. 단일 종합 점수 (7.5/10 류) BANNED — 점수는 actionable 하지 않다.
Workflow for a text-only model to self-verify visual craft (the loop)
Step 0 — Pre-build gate (no browser yet). Produce token system (4-6 color + ≥2 type roles + 1 signature element) as a structured artifact. Run the self-uniqueness test. Only then write code, deriving every value from the token system.
Step 1 — Render. browser_navigate → browser_resize mobile (390×812) AND desktop (1440×900).
Step 2 — Structural probe. browser_snapshot (a11y tree) + browser_console_messages (warnings).
Step 3 — Computed-style probe. browser_evaluate (template above). Assert hex values match the token system; flag any purple/cream/Inter defaults; check font actually loaded; compute contrast; count transitions.
Step 4 — Rubric scoring. Fill the 7-dim table from probe values (not vibes). Any FAIL → revise → re-run Steps 1-3.
Step 5 — Slop gate. Run the 13-tell checklist from probe values. Hard-fail tells: vague headline, Inter-only, purple gradient, uniform sizing, decorative numbering, scattered motion. Any present → revise.
Step 6 — Restraint pass. Enumerate every decorative element (gradient, divider, eyebrow, motion) and force-remove at least one (Chanel rule). Re-probe.
Step 7 — Screenshot (audit artifact only). browser_take_screenshot (fullPage, mobile + desktop) → save. GLM-5.2 cannot interpret it; it exists for a human or vision-model reviewer and the audit trail. Do not treat a screenshot as GLM-5.2's own verification.
Loop termination: Steps 1-5 pass clean on both viewports + zero slop tells + one justified signature element present → ship.
Honesty limits
- This rubric is rule-application over probe output — a strong signal, not ground truth. Keep real backstops: human merge, axe CI for formal WCAG (defer there — never self-report axe), runtime verification for behavior, visual-regression CI for change.
- A probe can lie (CSS specificity cancellation, late hydration, fonts loading after probe). Mitigate:
browser_wait_for anchor before probe; document.fonts.ready await; cross-check browser_console_messages for overrides.
- The model cannot catch what the probe does not return. If a craft dimension is not in the probe output, do NOT fabricate a judgment — mark it "확인 불가 (probe 미수행)".