| name | ui-validate |
| description | Unified UI validation skill โ hierarchy (Playwright DOM saliency + WCAG AA), motion (CSS transition vs RESPECT.md drift), drift (pHash N-screen consistency), mobile (375/768/1440px breakpoint). Each check is independently runnable and independently failable. --check ์ธ์ ๋ฏธ๋ช
์ ์ ์๋ฌ ์ถ๋ ฅ ํ ์ฌ์ฉ ๊ฐ๋ฅํ check ๋ชฉ๋ก ์๋ด โ auto-run ์ ๋ ๊ธ์ง., tc-gate (QA_CHECKLIST.md TC-ID๋ณ Playwright ์คํฌ๋ฆฐ์ท ์ฆ๊ฑฐ ์์ฑ โ harness/ui-evidence/) |
| argument-hint | --check hierarchy|motion|drift|mobile|tc-gate [url or path or src dir] |
| allowed-tools | ["Read","Write","Bash"] |
| model | inherit |
Core Goal
4์ข
UI ๊ฒ์ฆ check๋ฅผ ๋จ์ผ ์ธํฐํ์ด์ค๋ก ํตํฉํ๋ค:
| check | ์ฑ
์ | ์ถ๋ ฅ ํ์ผ | LLM |
|---|
--check hierarchy | Playwright + DOM saliency + WCAG AA ์๊ฐ ๊ณ์ธต ์ธก์ | .design/hierarchy-report.json | โ |
--check motion | CSS transition vs RESPECT.md motion_language ์ผ๊ด์ฑ | .design/motion-drift.md | โ |
--check drift | N ํ๋ฉด pHash + DOM ๊ตฌ์กฐ ์๊ฐ drift ๊ฐ์ง | .design/ui-drift-report.md | โ |
--check mobile | 375/768/1440px ๋ธ๋ ์ดํฌํฌ์ธํธ Playwright ๊ฒ์ฆ | mobile-check-report.md | โ |
--check tc-gate | QA_CHECKLIST.md TC-ID๋ณ Playwright ์คํฌ๋ฆฐ์ท โ harness/ui-evidence/ | harness/ui-evidence/summary.json | โ |
โ ๏ธ --check ์ธ์ ํ์: ๋ฏธ๋ช
์ ์ ์๋ฌ ์ถ๋ ฅ ํ ์ฌ์ฉ ๊ฐ๋ฅํ check ๋ชฉ๋ก ์๋ด. auto-run ์ ๋ ๊ธ์ง.
Rule 5 ์ค์ โ ๋ชจ๋ ์ธก์ ์ด ๊ฒฐ์ ๋ก
| ๊ฒ์ฆ | ๋๊ตฌ | LLM |
|---|
| fold_density, type_hierarchy, color_60_30_10, whitespace, cta_count | Playwright DOM + pixel | โ |
| WCAG AA contrast | WebAIM relative luminance ์๊ณ ๋ฆฌ์ฆ | โ |
| CSS transition duration/easing ์ถ์ถ | ์ ๊ท์ + CSSOM | โ |
| framer-motion props | AST ํ์ | โ |
| pHash ๋น๊ต | imagehash library | โ |
| ์์ palette | KMeans on screenshot | โ |
| ๋ธ๋ ์ดํฌํฌ์ธํธ Playwright | npx playwright test | โ |
์คํจ ์ฒ๋ฆฌ ์์น
- ๊ฐ check๋ ๋
๋ฆฝ ์คํจ ๊ฐ๋ฅ โ ํ check ์คํจ๊ฐ ๋ค๋ฅธ check๋ฅผ ๋ง์ง ์์
- baseline ์์ผ๋ฉด SKIP (FAIL ์๋) โ baseline ๋ถ์ฌ๋ ๋ฐ์ดํฐ ๋ฏธ๋น์ด์ง ์๋ฐ์ด ์๋
- ํ์์์: 30์ด/check โ ์ด๊ณผ ์ ํด๋น check SKIP + warning
- **respect (--mode checkpointยท--mode brief)**๋ ์ด ์คํฌ์ ํตํฉํ์ง ์์
Trigger Gate
Use This Skill When
- craft-lint ์ ์ ๊ฒ์ฆ ํต๊ณผ ํ ๋ฐํ์ ์ธก์ โ
--check hierarchy
- ๋ฆฌ๋์์ธ ํ motion ์ผ๊ด์ฑ ๊ฒ์ฆ โ
--check motion
- 5+ ํ๋ฉด ์์ฑ ํ ์ผ๊ด์ฑ ํ์ธ โ
--check drift
- ship ์ง์ ๋ชจ๋ฐ์ผ ๋ธ๋ ์ดํฌํฌ์ธํธ ๊ฒ์ฆ โ
--check mobile
- qa-checklist ์คํ ํ TC-ID๋ณ ๋ธ๋ผ์ฐ์ ์คํฌ๋ฆฐ์ท ์ฆ๊ฑฐ ์์ฑ โ
--check tc-gate [URL]
Route to Other Skills When
- RESPECT.md ๊ฐฑ์ โ
deliver/respect --mode brief
- ship ์ง์ ์ข
ํฉ ๊ฒ์ดํธ โ
deliver/respect --mode checkpoint
- ๋์์ธ ํ ํฐ ๊ฐฑ์ โ ํ๋ก์ ํธ DESIGN.md
Boundary Checks
--check ๋ฏธ๋ช
์ โ ์ฆ์ ์๋ฌ + ์ฌ์ฉ ๊ฐ๋ฅํ check ๋ชฉ๋ก ์ถ๋ ฅ (auto-run ๊ธ์ง)
harness/QA_CHECKLIST.md ๋ถ์ฌ โ fail loud + "qa-checklist ๋จผ์ ์คํํ์ธ์"
- Playwright ๋ฏธ์ค์น โ fail loud + ์ค์น ์๋ด
- RESPECT.md ๋ถ์ฌ ์ hierarchy/motion โ fail loud + "respect --mode brief ๋จผ์ "
- DESIGN.md ๋ถ์ฌ ์ mobile โ fail loud + "design-token ๋จผ์ "
Inputs
| ์
๋ ฅ | ์ถ์ฒ | ์ฒ๋ฆฌ |
|---|
--check | $ARGUMENTS | hierarchy/motion/drift/mobile ๋ถ๊ธฐ |
| target (URL/path/dir) | $ARGUMENTS (check ์ดํ ๋๋จธ์ง) | ๊ฐ check ๋ด๋ถ ์ฒ๋ฆฌ |
| RESPECT.md | .design/RESPECT.md | hierarchy/motion ๊ธฐ์ค |
| DESIGN.md | ํ๋ก์ ํธ ๋ฃจํธ ๋๋ .design/DESIGN.md | mobile ๋ธ๋ ์ดํฌํฌ์ธํธ |
Instructions
You are running ui-validate with arguments: $ARGUMENTS
๊ณตํต Step 0 โ check ํ์ฑ
args = parse("$ARGUMENTS")
check = args.get("--check", None)
target = args remainder after --check value
--check ๋ฏธ๋ช
์ ์:
โ ์๋ฌ: --check ์ธ์๊ฐ ํ์ํฉ๋๋ค.
์ฌ์ฉ ๊ฐ๋ฅํ check:
--check hierarchy Playwright DOM saliency + WCAG AA ์๊ฐ ๊ณ์ธต ์ธก์
--check motion CSS transition vs RESPECT.md motion ์ผ๊ด์ฑ
--check drift N ํ๋ฉด pHash ๋น๊ต ์๊ฐ drift ๊ฐ์ง
--check mobile 375/768/1440px ๋ธ๋ ์ดํฌํฌ์ธํธ ๊ฒ์ฆ
์์: ui-validate --check hierarchy https://habix.ai
ui-validate --check mobile http://localhost:3000
check: hierarchy
๋์: URL ๋๋ HTML ๊ฒฝ๋ก
Step 1 โ Playwright + RESPECT.md ๋ก๋
npx playwright install ํ์ธ
.design/RESPECT.md yaml ํ์ฑ โ hierarchy_rules ์น์
- RESPECT.md ๋ถ์ฌ ์ fail loud
Step 2 โ 5 ๋ฃฐ + WCAG ๊ฒฐ์ ๋ก ์ธก์ (LLM 0)
page = playwright.new_page(viewport={"width": 1440, "height": 1080})
page.goto(target)
elements = page.eval("...above-fold elements count...")
fold_pass = elements <= rules["max_elements_above_fold"]
sizes_px = sorted(unique font sizes from CSSOM, reverse=True)
ratios = [sizes_px[i] / sizes_px[i+1] for i]
type_pass = len(sizes_px) <= rules["max_type_scale"] and all(r >= 1.25 for r)
screenshot โ KMeans(3) โ percentages
color_pass = all(abs(pct[i] - target[i]) <= 10 for i in range(3))
light_mask = pixels > 240; ratio = light_mask.sum() / total
whitespace_pass = ratio >= rules["whitespace_min"]
ctas_above = [c for c in page.locator("button, a[role='button'], .cta").all() if y < 1080]
cta_pass = len(ctas_above) == rules["cta_count_above_fold"]
all text elements โ (foreground, background) โ relative luminance ratio
wcag_violations = [e for e if ratio < 4.5 (normal) or < 3.0 (large 18pt+)]
Step 3 โ .design/hierarchy-report.json ์ถ๋ ฅ
Step 4 โ ํต๊ณผ/์คํจ ํ์
- ๋ชจ๋ pass โ "hierarchy check PASS"
- ์คํจ ์ element selector + fix ๊ถ์
check: motion
๋์: src ๋๋ ํฐ๋ฆฌ ๋๋ ์ปดํฌ๋ํธ ํ์ผ ๊ฒฝ๋ก
Step 1 โ RESPECT.md motion_language ๋ก๋
- hover_transition_ms / page_transition_easing / scroll_reveal
- ๋ถ์ฌ ์ fail loud
Step 2 โ ํ์ผ ๊ฒฐ์ ๋ก ์ค์บ (LLM 0)
grep -rn -E "transition:|duration-[0-9]+|cubic-bezier\(" $target \
--include="*.css" --include="*.scss" --include="*.tsx" --include="*.jsx" --include="*.vue"
Step 3 โ framer-motion props ์ถ์ถ (AST)
transition={{ duration: N }} ํจํด โ ๋จ์ ๋ณํ (s โ ms)
Step 4 โ RESPECT ๋ช
์ธ์ ๋น๊ต
- hover_transition_ms ยฑ 50ms tolerance
- easing ํจ์ ๋ฌธ์์ด ์์ ์ผ์น
- ์๋ฐ ์ฌํญ ๋ฆฌ์คํธ
Step 5 โ .design/motion-drift.md ์ถ๋ ฅ
Step 6 โ ํต๊ณผ/์คํจ ํ์
- drift 0 โ PASS
- drift โฅ 1 โ FAIL + ํ์ผยท๋ผ์ธ ๋ช
์
check: drift
๋์: URL ๋ฆฌ์คํธ (์ผํ ๊ตฌ๋ถ) ๋๋ ์คํฌ๋ฆฐ์ท ๋๋ ํฐ๋ฆฌ ๊ฒฝ๋ก
Step 1 โ ํ๋ฉด ์บก์ฒ ๋๋ ์คํฌ๋ฆฐ์ท ๋ก๋
- URL ์
๋ ฅ โ Playwright ๋ค์ค navigate + screenshot
- ๋๋ ํฐ๋ฆฌ ์
๋ ฅ โ PNG ํ์ผ ๋ก๋
Step 2 โ ๊ฐ ํ๋ฉด ๋ฉํธ๋ฆญ ๊ฒฐ์ ๋ก ์ถ์ถ
- pHash (16-bit perceptual hash)
- color palette top 5 (KMeans)
- font-family set (DOM scrape)
- spacing ๋ชจ๋๊ฐ (margin/padding ๋น๋ top 3)
- DOM tree depth + node count
Step 3 โ baseline ๋๋น drift score ๊ณ์ฐ (LLM 0)
drift = {
"phash": hamming_distance(baseline.phash, current.phash),
"color": palette_distance(baseline.palette, current.palette),
"font": jaccard_distance(baseline.fonts, current.fonts),
"spacing": rmse(baseline.spacing, current.spacing),
"structure": tree_edit_distance(baseline.dom, current.dom),
}
Step 4 โ drift ์๊ณ์น ๋น๊ต
- phash > 40: drift
- color: palette ๋ณ๊ฒฝ 1๊ฐ ์ด์ + ๋น์ค > 10%: drift
- font: ๊ธฐ์กด family ์ธ ์ font: drift
- spacing: rmse > 4px: drift
- structure: edit_distance > 30: drift
Step 5 โ .design/ui-drift-report.md ์ถ๋ ฅ
Step 6 โ ์ข
ํฉ verdict
- drift 0 โ PASS
- drift โฅ 1 ํ๋ฉด โ fix ๊ถ์ + design-system ํ๊ท ์์ฌ
check: mobile
๋์: URL ๋๋ ๊ธฐ๋ณธ๊ฐ http://localhost:3000
Step 1 โ DESIGN.md ์กด์ฌ ํ์ธ
- ์์ผ๋ฉด fail loud + "design-token ๋จผ์ "
Step 2 โ Playwright ํ๊ฒฝ ํ์ธ
npx playwright --version 2>/dev/null || echo "not installed"
Step 3 โ Playwright ํ
์คํธ ํ์ผ ํ์ธ/์์ฑ
tests/design/mobile-check.spec.ts ์์ผ๋ฉด references/playwright-setup.md ๊ธฐ๋ฐ ์์ฑ
Step 4 โ 3 ๋ทฐํฌํธ ์คํ
TARGET_URL=${target:-http://localhost:3000} \
npx playwright test tests/design/mobile-check.spec.ts --reporter=line 2>&1
Step 5 โ ASCII ๊ฒฐ๊ณผ ์ถ๋ ฅ
โโโโ mobile-check ๊ฒฐ๊ณผ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 375px โ
/โ N/3 โ
โ 768px โ
/โ N/3 โ
โ 1440px โ
/โ N/1 โ
โ ์ด์: (์์ ๋๋ง) โ
โ ์์ ํ ์ฌ์คํ: ui-validate --check mobile โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Step 6 โ ๋ฆฌํฌํธ ์ ์ฅ
- ์ด์ 1๊ฐ ์ด์ โ
mobile-check-report.md ์์ฑ
- ์ด์ ์์ โ ํ์ผ ์์ฑ ์ ํจ
check: tc-gate
๋์: URL (๋ฐฐํฌ๋ ์ฑ ๋๋ localhost)
Step 1 โ QA_CHECKLIST.md ๋ก๋
ls harness/QA_CHECKLIST.md 2>/dev/null || echo "CHECKLIST_MISSING"
CHECKLIST_MISSING ์:
โ ์๋ฌ: harness/QA_CHECKLIST.md ์์.
/qa-checklist ๋จผ์ ์คํํ์ธ์.
์ฆ์ ์ข
๋ฃ.
Step 2 โ TC ๋ชฉ๋ก ์ถ์ถ (๊ฒฐ์ ๋ก )
python3 deliver/skills/ui-validate/scripts/pw_runner.py --parse-only harness/QA_CHECKLIST.md
TC-ID / severity / ์๋๋ฆฌ์ค ๋ชฉ๋ก ์ถ๋ ฅ. critical โ major โ minor ์ ์ ๋ ฌ.
QA_CHECKLIST Expected State ์ปฌ๋ผ (์ ํ)
TC์ Playwright assertion์ ์ถ๊ฐํ๋ ค๋ฉด 8๋ฒ์งธ ์ปฌ๋ผ์ผ๋ก Expected State๋ฅผ ์ถ๊ฐํ๋ค.
์ปฌ๋ผ์ด ์๊ฑฐ๋ โ์ด๋ฉด ๊ธฐ์กด ์คํฌ๋ฆฐ์ท ์ ์ฉ ๋ชจ๋๋ก ๋์ํ๋ค.
| TC-ID | ์๋๋ฆฌ์ค | ํ๊ฒฝ | ์ ์ ์กฐ๊ฑด | ๊ธฐ๋๊ฒฐ๊ณผ | PRD์ถ์ฒ | Expected State | Severity |
|-------|----------|------|----------|----------|---------|----------------|----------|
| TC-001 | ๋ก๊ทธ์ธ ์ฑ๊ณต | Chrome | ์ ํจํ ์๊ฒฉ์ฆ๋ช
| ๋์๋ณด๋ ์ด๋ | ยง11 | url_contains:/dashboard | critical |
| TC-002 | ์ค๋ฅ ๋ฉ์์ง ํ์ | Chrome | ์๋ชป๋ ๋น๋ฐ๋ฒํธ | ์๋ฌ ๋ฉ์์ง ๋
ธ์ถ | ยง14 | element_text:#error-msg:์
๋ ฅ ์ค๋ฅ | major |
| TC-003 | ๋ฒํผ ์ํ | Chrome | โ | ๋ฒํผ ํ์ฑํ | ยง11 | element_exists:#submit-btn | minor |
| TC-004 | ํ๋ฉด ์ผ๋ฐ ์ ๊ฒ | Chrome | โ | ๋ ์ด์์ ์ ์ | ยง11 | โ | minor |
์ง์ assertion ํ์
3๊ฐ์ง:
url_contains:<path> โ ํ์ฌ URL์ path๊ฐ ํฌํจ๋๋์ง
element_exists:<selector> โ CSS selector ์์ ์กด์ฌ ์ฌ๋ถ
element_text:<selector>:<text> โ ์์ ํ
์คํธ์ text๊ฐ ํฌํจ๋๋์ง
gstack /browse ๋๋น ์ฐจ๋ณ์ : assertion์ด TC-ID โ QA_CHECKLIST โ PRD ยง11/ยง14๋ก ์ถ์ ๊ฐ๋ฅ.
Step 3 โ ์ถ๋ ฅ ๋๋ ํฐ๋ฆฌ ์ค๋น
mkdir -p harness/ui-evidence
Step 4 โ Playwright ์คํฌ๋ฆฐ์ท ์คํ
python3 deliver/skills/ui-validate/scripts/pw_runner.py \
--url "$TARGET_URL" \
--checklist harness/QA_CHECKLIST.md \
--output harness/ui-evidence
๊ฐ TC-ID๋ณ:
- Playwright ๋ธ๋ผ์ฐ์ โ URL ๋ค๋น๊ฒ์ดํธ
- ์ ์ฒด ํ๋ฉด ์คํฌ๋ฆฐ์ท โ
harness/ui-evidence/[TC-ID].png
- ๊ฒฐ๊ณผ ๊ธฐ๋ก (captured / error)
Step 5 โ summary.json ์์ฑ
pw_runner.py๊ฐ ์๋ ์์ฑ:
{
"generated": "YYYY-MM-DD",
"url": "...",
"total": N,
"critical_captured": X,
"critical_total": X2,
"major_captured": Y,
"major_total": Y2,
"minor_captured": Z,
"minor_total": Z2,
"tc_results": [
{"id": "TC-001", "severity": "critical", "screenshot": "harness/ui-evidence/TC-001.png", "status": "captured"},
{"id": "TC-002", "severity": "critical", "screenshot": "harness/ui-evidence/TC-002.png", "status": "error", "error": "timeout"}
]
}
Step 6 โ ๊ฒฐ๊ณผ ์ถ๋ ฅ
โ
harness/ui-evidence/ ์์ฑ ์๋ฃ
Total: N | Critical: X/X2 | Major: Y/Y2 | Minor: Z/Z2
Critical assertion ์คํจ: K๊ฑด โ assertion ์คํจ ์์ ๋๋ง ์ถ๋ ฅ
์คํจ: [error ๋ชฉ๋ก (์์ ๋๋ง)]
โ harness-build --step quality-gate ์์ ์๋ ๊ฒ์ฌ
Failure Handling
| ์คํจ ์ํฉ | ๊ฐ์ง | ๋์ |
|---|
| --check ๋ฏธ๋ช
์ | ํ์ฑ ๊ฒฐ๊ณผ None | ์๋ฌ ์ถ๋ ฅ + check ๋ชฉ๋ก ์๋ด, ์คํ ์ค๋จ |
| Playwright ๋ฏธ์ค์น | which playwright ์คํจ | npm install playwright && npx playwright install ๊ถ์ |
| RESPECT.md ๋ถ์ฌ (hierarchy/motion) | file not found | "deliver/respect --mode brief ๋จผ์ " fail loud |
| DESIGN.md ๋ถ์ฌ (mobile) | file not found | "design-token ๋จผ์ " fail loud |
| baseline ์์ (drift) | ํ๋ฉด ์ < 2 | SKIP (FAIL ์๋) + warning |
| ํ์์์ 30์ด ์ด๊ณผ | per-check timeout | ํด๋น check SKIP + warning, ๋๋จธ์ง ๊ณ์ |
| ํ๋ฉด ์ < 3 (drift) | argument count | "์ต์ 3 ํ๋ฉด ํ์" warning + SKIP |
harness/QA_CHECKLIST.md ๋ถ์ฌ | ls ์คํจ | fail loud + "qa-checklist ๋จผ์ ์คํํ์ธ์" ํ ์ข
๋ฃ |
Playwright ๋ฏธ์ค์น (tc-gate) | python3 import ์คํจ | pip install playwright && playwright install ์๋ด |
| URL ์๋ต ์์ | Playwright timeout | ํด๋น TC status="error" ๋ก ๊ธฐ๋ก, ๋๋จธ์ง ๊ณ์ ์งํ |
Quality Gate
Examples
Good Example
์
๋ ฅ: --check hierarchy https://habix.ai
๊ธฐ๋ ๋์:
- Playwright 1440ร1080 navigate
- 5 ๋ฃฐ + WCAG AA ๊ฒฐ์ ๋ก ์ธก์
- hierarchy-report.json ์ถ๋ ฅ
- ์คํจ ํญ๋ชฉ element selector ๋ช
์
Good Example
์
๋ ฅ: --check mobile
๊ธฐ๋ ๋์:
- DESIGN.md ํ์ธ
- 3 ๋ทฐํฌํธ (375/768/1440) Playwright ์คํ
- ASCII ๊ฒฐ๊ณผ ์ถ๋ ฅ
- ์คํจ ์ mobile-check-report.md ์์ฑ
Bad Example
์
๋ ฅ: ui-validate (--check ์์)
๊ธฐ๋ ๋์:
โ ์๋ฌ: --check ์ธ์๊ฐ ํ์ํฉ๋๋ค.
์ฌ์ฉ ๊ฐ๋ฅํ check: hierarchy | motion | drift | mobile
์คํ ์ค๋จ. auto-run ๊ธ์ง.
Bad Example
์
๋ ฅ: --check hierarchy (RESPECT.md ์์)
๊ธฐ๋ ๋์: "RESPECT.md ๋ถ์ฌ โ deliver/respect --mode brief ๋จผ์ " fail loud
Good Example
์
๋ ฅ: --check tc-gate http://localhost:3000
๊ธฐ๋ ๋์:
- harness/QA_CHECKLIST.md ๋ก๋ โ TC-001~TC-NNN ์ถ์ถ
- critical TC ๋จผ์ ์คํฌ๋ฆฐ์ท
- harness/ui-evidence/ ์์ฑ + summary.json ์์ฑ
- ํต๊ณ ์ถ๋ ฅ
Bad Example
์
๋ ฅ: --check tc-gate http://localhost:3000 (QA_CHECKLIST.md ์์)
๊ธฐ๋ ๋์:
โ ์๋ฌ: harness/QA_CHECKLIST.md ์์.
/qa-checklist ๋จผ์ ์คํํ์ธ์.
์คํ ์ค๋จ.
Contextual Knowledge (auto-loaded)
Playwright Setup
!cat references/playwright-setup.md 2>/dev/null || echo ""
WCAG Algorithm
!cat references/wcag-algorithm.md 2>/dev/null || echo ""
pHash Calibration
!cat references/phash-tuning.md 2>/dev/null || echo ""
Tailwind Duration Mapping
!cat references/tailwind-duration.md 2>/dev/null || echo ""