WHAT
Purpose and context
- Purpose: give a SENIOR verdict on the UI/UX of the existing route/screen — four sections (accessibility · visual-hierarchy · consistency · antipattern), each with score/10 + most severe issue + how to fix — with the measurable part taken from a deterministic engine and the part needing eyes done by LLM audit.
- Trigger (when to use):
- Just built/changed UI (mockup
/br, new page, component) and want a senior pair of eyes on it before finalizing. - Before committing a significant UI change — plug the option into
/orca-workflowbeforeverify-before-commit, and automatically after each/br runframe that has UI. - User says "qc uiux", "audit ui", "soi giao diện", "dẹp antipattern ui", "/qc-uiux".
- Just built/changed UI (mockup
- Non-goals: NOT for: overall aesthetic redesign (that is
/redesign-existing-projects), or just screenshot+pixel comparison (that is/visual-qa— qc-uiux CALLS it as the measurement engine). Not CODE review (that is/qc-code). The verdict does not block commits.
Mental model
route/màn → engine tất định (DESIGN_AUDIT đo rect thật) = DỮ KIỆN → 4 mục × (điểm/10 · lỗi nặng nhất · cách sửa) = Ý KIẾN có dẫn số → verdict PASS | CẦN SỬA (theo mục/route yếu nhất) → sửa → engine lại (đỏ→xanh). Expensive (LLM, manual call) separated from cheap (engine, auto hook).
Input and output contract
| Field | Required? | Meaning | |
|---|---|---|---|
| In | route/screen | no (default = existing mockup/route) | audit that one if the user specifies it |
| In | <base-url> runnable |
yes for the measurement part | headless engine needs a server; if it cannot be brought up → fail-open |
| In | route type (list/table?) | determined automatically | yes → load references/list-control-checklist.md |
| Out | 4 sections × score/10 · most severe issue · how to fix | yes | section 3 includes a drift table; sections with measurable errors cite engine numbers |
| Out | verdict | yes | PASS or CẦN SỬA + list of things to fix — ADVISORY |
| Out | draft llmwiki/wiki/sources/draft/DDMMYY-qc-uiux-<route>.md + index + log |
yes (unless only running the engine) | section Output Report |
Rules and capabilities
- RULE-01 (MUST): Separate expensive/cheap: LLM audit (4 sections) = manual call / optional workflow step. Deterministic measurement = auto via the visual-qa engine. Do NOT call the LLM in a hook (hook-0-token principle).
- RULE-02 (MUST): Verdict advisory, engine measurements hard-gate — do not let the LLM verdict block commits; the measurements (contrast/tap-target/geometry) are the facts.
- RULE-03 (MUST): Reading CSS is BLIND to geometry — all layout/size/contrast assessments must MEASURE the actual rect via the engine, not infer from static CSS.
- RULE-04 (MUST): Do not step on:
/redesign-existing-projects= aesthetic renewal ·/visual-qa= capture+guard pixel-diff ·/qc-code= CODE review./qc-uiux= senior verdict 4-section UI/UX + reuse measuring engine. - RULE-05 (MUST): Function before aesthetics, no averaging of scores (learned from skill
qaof browser-use, 09/2026): if the route/flow being inspected has a user task (submit, checkout, login) and the task CANNOT be completed → verdictCẦN SỬAregardless of high scores in the 4 sections — beauty cannot save a broken flow. The overall verdict is taken by the WEAKEST section/route, not by arithmetic average. - RULE-06 (MUST): A clean screenshot proves nothing — when the engine runs headless, dig out more hidden errors: console
error,pageerror, response>= 400,requestfailed(see/playwright-verifyRules). A page that looks good but throws errors on every click → record it in section 4, do not give PASS. - RULE-07 (MUST): a11y is law, not taste — CLAUDE.md carve-out: do not be lazy about contrast/tap-target/label. Only ~30% of WCAG is auto-detectable → the rest explicitly note "needs human eye / debt
[[150726-unknown-ledger]]". - Capabilities: run the page in a headless browser to measure DOM/rect/contrast (0-token); read CSS/DOM; write draft wiki. Do not modify UI during audit.
Failure boundaries
- Cannot spin up server/headless → engine fail-open (do not block); the LLM audit still runs but must record the measurable part as having no numbers yet.
- A user task on the route cannot be completed → verdict
CẦN SỬAregardless of the 4-section score. - Console
error/pageerror/response>= 400/requestfailed→ record in section 4, do not give PASS. - WCAG criteria that cannot be auto-detected → explicitly note "needs human eye / debt
[[150726-unknown-ledger]]", do not self-grant PASS.
HOW
Main workflow
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W00 | judgment | route | Is the route a list/table? → load references/list-control-checklist.md |
supplementary checklist | no → skip |
| W01 | deterministic | route, <base-url> |
Determine scope + run engine route-shots.mjs --audit first |
issues by route | cannot spin up → fail-open, note |
| W02 | judgment | issues + DOM | Score four sections: score/10 · most severe error · how to fix | 4 sections with all three parts | — |
| W03 | deterministic | issues engine | Attach engine evidence to items with measurable errors | measurements cite the source | — |
| W04 | judgment | 4 items | Conclusion PASS / CẦN SỬA (per the weakest item/route) |
verdict + must-fix | — |
| W05 | deterministic | UI fixed | Re-run the engine to prove the measurable errors are gone (red→green) | engine green | still red → still NEEDS FIXING |
| W06 | effect | result | Write draft Output Report + index + log | draft | only running the engine → skip |
Step-by-step detail (source of truth for W00–W06):
Audit scope (default = existing mockup/route)
By default inspect the existing UI — the mockup routes/pages just built. If the user specifies a route/screen, inspect that one. Read the DOM + measure real rects (getBoundingClientRect) via the engine; do NOT guess from static CSS (reading CSS BLINDLY misses render bugs — lesson learned 15/07/26).
Steps
- Is the route a list/table? (queue, admin grid, ticket/request list, dashboard summary strip) → additionally load
references/list-control-checklist.md— 11 common errors specific to lists/tables (color-system stacking on rows, sort bucket, overflow-clip popover, mobile filter collapse…), each error is already mapped to one of the 4 items below. - Determine the scope — which route/screen. Run the deterministic engine first to get measurable EVIDENCE (the "Engine" item below).
- Score four items (items below). Each item: score/10 · most severe error · how to fix. → Done when all four items have all three parts.
- Items with MEASURABLE errors: attach engine evidence (where contrast fails, which control is <24px, which pair overlaps…) — measurements are facts, the verdict is an opinion.
- Conclusion —
PASS(move to the next step) orCẦN SỬA(list what must be fixed). → Done when the verdict is clear + the must-fix list if NEEDS FIXING. - Re-run the engine after fixing to prove the measurable errors are gone (red→green), like qc-code's reproduction test.
Four items
1. Accessibility (a11y) — score/10 · most severe error · how to fix
Inspect (mostly MEASURABLE — the engine catches them): contrast (body text ≥4.5:1, large text ≥3:1, controls/borders/icons ≥3:1 — WCAG 2.2 AA) · tap-target (interactive controls ≥24×24px CSS is the AA 2.5.8 floor; 44/48px recommended on mobile) · focus-visible (has a clear focus ring, contrast ≥3:1) · missing-label (button/icon/link screen readers read as empty — missing aria-label/text). This is the accessibility boundary — not laziness (a11y is LAW, not taste). Note: only ~30% of WCAG criteria can be automated → the rest (keyboard trap, focus order, MEANINGFUL alt) need human eyes.
2. Visual hierarchy — score/10 · most severe error · how to fix
Measurable rule (engine fdk/tools/html-visual-gate.mjs, PLAN 220926 — user 22/09/2026), runs BEFORE the LLM scores this item:
heading-scale(FAIL): present heading levels must go big → small (h1 > h2 > h3 > h4) and not be smaller than body text.title-scale(FAIL): page name (brand/logo/h1) ≥ 1.2 × the largest text of the nav item and content selection tabs.sentence-case(FAIL): headings, labels, buttons, tabs, nav items capitalize the first letter (exempt identifiers with digits/-_./:@, proper names, text in code; keep intentional lowercase withdata-case="keep").eye-rest(WARN): there must be EYE REST — first screen ≤ 55% is text/blocks and no continuous thick band > 520px lacking ≥ 24px of spacing; the framework's heuristic threshold. Cramming many co-equal things onto one screen (6 status pills + 3 chips + 3 buttons on one bar) is an error even if each is individually correct — merge, and let details appear on click (NN/g progressive disclosure).kanban-uniform(FAIL): kanban board (≥ 2 columnslane|kanban|[data-kanban-lane]) — all cards same width + same height (±2px) and same style (background · border · radius · padding · font); cards fixed-size, click to view details.- Button harmony (separate engine
fdk/tools/button-harmony.mjs, user 24/09/2026 — runs at 1440 AND 390, rc 0 required for PASS):lone-action(FAIL) button stands alone on a line, not the block-ending row, drifting > 50% of row width — secondary buttons must be ON THE SAME LINE as the heading/field they serve;edge-gap(FAIL) row aligned to the right edge but the last child falls short of the edge by > 8px;empty-occupant(FAIL) invisible empty cell > 48px taking up space in a horizontal row (except padding elementsflex:1);mixed-edge(WARN) button rows end in a block aligned to a different edge. The ruleaction-left(bottom-right corner priority) still holds — harmony is an ADDITIONAL condition, not a replacement. Pages requiring login: capture the rendered DOM to a file, strip app scripts, then run on the file. - Comparison standard: each UI part being audited has a same-type block in the sample page
skills/hallmark/references/design-showcase.html(client:~/.claude/skills/hallmark/references/design-showcase.html). Get the block viapython3 fdk/tools/build-design-showcase.py --get <id>(client:python3 ~/.claude/harness/fdk/tools/build-design-showcase.py --get <id>;--listin index) then compare each point (size, state, color token, motion); a mismatch the page doesn't declare an exemption for = error, write the standard block id in "how to fix". - Special requirements the page SELF-DECLARES:
<meta name="overstack-exempt" content="heading-scale,…" data-reason="…">— the audit must read the reason, not self-exempt. Inspect (LLM eyes): buttons look like buttons, links look like links, headings clearly ranked by size (forcing users to "decode" the interface = fail) · Clear CTA (primary action stands out, not ambiguous) · content density (don't cram too much onto one screen — overload = lost hierarchy). Point out where the FIRST eye-stop is and whether it really is the main action.
3. Consistency (design-system) — score/10 · worst error · drift table
Inspect (LLM eye, a few measurable parts): spacing by token (4/8px scale — messy gaps = lost rhythm) · consistent color (no clashing/mixed palettes) · shadow (no overuse/wrong depth — engine catches shadow-clipped/monochrome-surface) · typography (consistent font/size pairing) · cross-page uniformity (the same thing is called the same way on every screen). Return the drift table:
| Where | Standard deviation | Fix to |
|---|---|---|
| card padding | 14px / 20px / 17px mixed | token --sp-4 (16px) |
4. Antipattern & interaction — score/10 · worst error · measurement evidence
Inspect: dark-mode readability (text washed-out too light/too dark — the classic dark-mode error, engine catches it via contrast-aa in the dark theme) · broken geometry (overlap ≥4px, row-misalign — controls overlapping/not equal height, engine MEASURES rect) · missing feedback (action with no system response) · broken responsive (horizontal overflow, overlap at narrow widths) · dark-pattern (coercion, trapped choices, hard to exit). Each measurable error → cite numbers from the engine (which route, which pair, overlapping by how many px).
Verdict
One of the two, with reason:
- PASS — no major error in any section; measurable a11y is clean; can move to the next step.
- NEEDS FIXING — list specifically what must be fixed (prioritize measurable a11y + broken geometry before hierarchy/consistency).
The verdict is ADVISORY — a human decides, it doesn't block commits. What gates hard is the deterministic engine (contrast/tap-target/overlap/misalign red→green). Don't let the user think "qc-uiux PASS = perfect UI"; it's a pair of senior eyes, not evidence.
Deterministic engine (0-token, NO LLM) — reuse visual-qa
The MEASURABLE part uses the shared engine of /visual-qa (skills/visual-qa/assets/route-shots.mjs, headless playwright), function DESIGN_AUDIT measures the real rect on the page and catches the rules: contrast-aa · tap-target · missing-label · overlap · row-misalign · shadow-clipped · monochrome-surface · rogue-slab. Run:
node skills/visual-qa/assets/route-shots.mjs <base-url> --audit # in issues theo route; exit ≠0 nếu a11y/hình học fail
This is the "auto-hook when the UI changes" part — only hook the cheap deterministic part, the LLM audit (this skill, 4 sections) stays manually invoked. Fail-open if the server/headless can't be set up yet (doesn't block). Like qc-regression.py --run of qc-code: verify-before-commit step 3b call it; UI changes → antipattern measurable does not silently return.
Branches
| ID | Kind | Guard | Behavior | Skip / failure | Rejoin |
|---|---|---|---|---|---|
| B01 | conditional_required | route is list/table (queue, admin grid, ticket/request list, dashboard summary strip) | load references/list-control-checklist.md, 11 errors attached to 4 items |
other route → skip | W01 |
| B02 | recovery | cannot build server/headless | engine fail-open (no blocking), audit LLM continues, record the measurable part as having no numbers yet | — | W02 |
| B03 | conditional_required | route has a user task (submit, checkout, login) that cannot be completed | verdict CẦN SỬA regardless of the 4-item scores |
— | W04 |
Validation and stopping
The measurable part (contrast, tap-target, overlap, misalign, missing-label…) is decided by a deterministic engine, exit ≠0 when fail; the hierarchy/drift/dark-pattern part is LLM review, verdict is only advisory. Stop when the 4 items have all three parts + a clear verdict; after fixing, the engine must be green (W05).
Examples
- Positive:
/qc-uiuxafter/br runframe the registration form → engine clean, 4 items 8–9/10, worst error item 3 is card padding 14/20/17px → drift table to--sp-4→ verdictPASS, draftDDMMYY-qc-uiux-signup.md. - Boundary/failure: dark mode secondary text contrast 2.8:1 + icon button 20×20px → engine
contrast-aa+tap-targetfail with numbers → item 1 worst error + how to fix →CẦN SỬA; after fixing, rerun the engine and it must be green. - Boundary: page is beautiful, 4 items score high but the submit button throws
pageerror→ item 4 records the error, verdictCẦN SỬA.
Reference — Related
references/list-control-checklist.md— 11 errors specific to list/table (read Steps step 0).skills/visual-qa/assets/route-shots.mjs— deterministic measurement engine (DESIGN_AUDIT) that this skill reuses.llmwiki/skills/dev-loop/qc-code.md— sibling skill: same expensive-LLM / cheap-deterministic template, for CODE.harness/scripts/qc-regression.py— deterministic qc-* test runner (qc-code) runs at verify-before-commit.llmwiki/skills/dev-loop/build-now-adapt-later.md— quA template splits expensive/cheap quarantine.
Delivery — Output Report
After the audit is done, write draft llmwiki/wiki/sources/draft/DDMMYY-qc-uiux-<route>.md (OKF frontmatter type: draft, ## Origin) summarizing: route inspected · 4-item scores · measurable errors from the engine · verdict + must-fix. Add a line to index.md + log.md. Skip if only running the measurement engine (no new judgment).