diff --git a/apps/site/gate-report.md b/apps/site/gate-report.md index decc570..bd4f2f9 100644 --- a/apps/site/gate-report.md +++ b/apps/site/gate-report.md @@ -1,4 +1,4 @@ -# 게이트 리포트 — 2026-08-23 06:40 +# 게이트 리포트 — 2026-08-23 04:48 **전 항목 통과 — 배포 가능** @@ -65,15 +65,6 @@ PASS gaon-lms/calendar 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px PASS gaon-lms/counsel 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px PASS gaon-lms/report 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px PASS gaon-lms/notice 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px -PASS gaon-lms/dashboard 왼쪽 인셋 — 최소 텍스트 시작 16px -PASS gaon-lms/students 왼쪽 인셋 — 최소 텍스트 시작 15px -PASS gaon-lms/attendance 왼쪽 인셋 — 최소 텍스트 시작 16px -PASS gaon-lms/grades 왼쪽 인셋 — 최소 텍스트 시작 16px -PASS gaon-lms/files 왼쪽 인셋 — 최소 텍스트 시작 16px -PASS gaon-lms/calendar 왼쪽 인셋 — 최소 텍스트 시작 16px -PASS gaon-lms/counsel 왼쪽 인셋 — 최소 텍스트 시작 16px -PASS gaon-lms/report 왼쪽 인셋 — 최소 텍스트 시작 16px -PASS gaon-lms/notice 왼쪽 인셋 — 최소 텍스트 시작 16px PASS dure-enrollment SEO/meta — title 15자·desc 79자·OG✓·alt 0결 PASS dure-enrollment/catalog 오버플로 — 320/375/390/768/1440 PASS dure-enrollment/catalog h1 1줄 @@ -113,12 +104,6 @@ PASS dure-enrollment/enrollments 터치타깃 — 컨트롤 ≥44px·인라인 PASS dure-enrollment/credits 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px PASS dure-enrollment/archive 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px PASS dure-enrollment/notices 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px -PASS dure-enrollment/catalog 왼쪽 인셋 — 최소 텍스트 시작 24px -PASS dure-enrollment/starred 왼쪽 인셋 — 최소 텍스트 시작 24px -PASS dure-enrollment/enrollments 왼쪽 인셋 — 최소 텍스트 시작 24px -PASS dure-enrollment/credits 왼쪽 인셋 — 최소 텍스트 시작 24px -PASS dure-enrollment/archive 왼쪽 인셋 — 최소 텍스트 시작 24px -PASS dure-enrollment/notices 왼쪽 인셋 — 최소 텍스트 시작 24px PASS L3 시각 회귀 — SKIP — checks.visual=false PASS L4 WebKit — SKIP — checks.webkit=false ``` diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 420ef6d..694fc83 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,11 +1,5 @@ # designpaca -## 0.8.0 - -### Minor Changes - -- 왼쪽 인셋 리듬·픽셀 판정을 스킬에 내재화 — design-gate leftInset 검사(텍스트 노드 Range 기준 ≥12px, 모바일 390px 전 뷰), 브랜드 검사 오탐 수정(보이는 요소 기준), preflight §0-E(왼쪽 인셋 리듬), 하니스 규칙 15~17(기준 갱신은 의도 변경 리뷰 동반 — 결함이 기준에 박히는 것 방지, 사용자 스크린샷 픽셀 판정 해부, 새 검사 RED 재현 후 GREEN), 사고표 4행(스크롤 잔류·빈약한 헤더·인셋 불일치), SKILL.md 규칙 6 보강(계측의 공백 — 보고가 오면 픽셀 판정 후 게이트에 추가) - ## 0.7.3 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index b5f64ff..2ab1fac 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "designpaca", - "version": "0.8.0", + "version": "0.7.3", "description": "웹 디자인 파이프라인 스킬 — Claude Code · Codex · Cursor 에 한 줄로 설치한다", "keywords": [ "design", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 0531e05..c8ecc06 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,7 +1,5 @@ # @designpaca/core -## 0.8.0 - ## 0.7.3 ## 0.7.2 diff --git a/packages/core/package.json b/packages/core/package.json index 11f08df..5733c85 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@designpaca/core", - "version": "0.8.0", + "version": "0.7.3", "private": true, "description": "designpaca 설치 엔진 — 타깃 어댑터, 매니페스트, 드리프트 감지", "type": "module", diff --git a/packages/skill/CHANGELOG.md b/packages/skill/CHANGELOG.md index aa4141f..bb98a41 100644 --- a/packages/skill/CHANGELOG.md +++ b/packages/skill/CHANGELOG.md @@ -1,11 +1,5 @@ # CHANGELOG -## 0.8.0 - -### Minor Changes - -- 왼쪽 인셋 리듬·픽셀 판정을 스킬에 내재화 — design-gate leftInset 검사(텍스트 노드 Range 기준 ≥12px, 모바일 390px 전 뷰), 브랜드 검사 오탐 수정(보이는 요소 기준), preflight §0-E(왼쪽 인셋 리듬), 하니스 규칙 15~17(기준 갱신은 의도 변경 리뷰 동반 — 결함이 기준에 박히는 것 방지, 사용자 스크린샷 픽셀 판정 해부, 새 검사 RED 재현 후 GREEN), 사고표 4행(스크롤 잔류·빈약한 헤더·인셋 불일치), SKILL.md 규칙 6 보강(계측의 공백 — 보고가 오면 픽셀 판정 후 게이트에 추가) - ## 0.7.3 ### Patch Changes diff --git a/packages/skill/SKILL.md b/packages/skill/SKILL.md index f394174..a683681 100644 --- a/packages/skill/SKILL.md +++ b/packages/skill/SKILL.md @@ -34,7 +34,7 @@ description: "웹 디자인 전 과정을 끌고 가는 파이프라인 스킬. 3. **화려함은 4순위다.** Awwwards 배점이 Design 40 / Usability 30 / Creativity 20 / Content 10이다. 3D를 얹는 것보다 타입 스케일을 정돈하는 게 결과에 두 배 유효하다. 4. **대담함은 한 곳에만.** 리스크는 하나다. 나머지는 조용히 받쳐준다. 두 곳에서 소리치면 둘 다 죽는다. 5. **성능 예산을 넘기면 그 이펙트는 채택하지 않는다.** 예산은 3단계에서 정하고 5단계에서 검증한다. 데모·포트폴리오 브리프면 예산을 올려도 되지만 **올렸다고 말해야 한다**. -6. **완료의 정의는 게이트 통과다(자율 검증 폐쇄 루프).** 5단계에서 `tools/design-gate.mjs` 를 프로젝트에 심어 돌리고, 실패 항목이 있으면 4단계로 돌아가 고치고 **전체를 재실행**한다. 게이트 없이 6단계로 넘어가지 않는다. 사용자를 QA 로 쓰지 않는다 — 검증은 기본 내장, 사용자 피드백은 보조다. 상세는 `references/audit-gate.md`. **게이트는 계측이고 계측엔 공백이 있다**(왼쪽 인셋·시각적 무게는 오버플로 검사를 통과한다). 사용자 보고가 오면 방어하지 말라 — 스크린샷을 픽셀 판정으로 해부해 원인을 수치화하고, 그 검사를 게이트에 추가해 재발을 막는다(하니스 규칙 15~17). +6. **완료의 정의는 게이트 통과다(자율 검증 폐쇄 루프).** 5단계에서 `tools/design-gate.mjs` 를 프로젝트에 심어 돌리고, 실패 항목이 있으면 4단계로 돌아가 고치고 **전체를 재실행**한다. 게이트 없이 6단계로 넘어가지 않는다. 사용자를 QA 로 쓰지 않는다 — 검증은 기본 내장, 사용자 피드백은 보조다. 상세는 `references/audit-gate.md`. 7. **OS·플랫폼 관습은 조사 없이 바꾸지 않는다.** 내비 구조·뒤로가기·시트·safe area·터치 타깃 같은 관습은 사용자가 이미 OS 전체에서 배운 것이다. "웹에서는 그랬다"를 폰에 옮기면 백 키가 앱을 종료하는 웹앱이 된다(실측 사고). 바꾸기 전에 HIG·M3·NN/g 에서 근거를 찾고, 모바일 앱 수준 브리프면 `references/mobile-app-ux.md` 를 구현 전에 읽는다. **접근성은 예외다. 이것만은 협상하지 않는다.** 키보드·포커스·대비·`prefers-reduced-motion`은 어떤 이펙트보다, 어떤 브리프보다 우선한다. 픽셀은 왜곡해도 DOM은 살린다. diff --git a/packages/skill/package.json b/packages/skill/package.json index 892fdf2..2ae8f0d 100644 --- a/packages/skill/package.json +++ b/packages/skill/package.json @@ -1,6 +1,6 @@ { "name": "@designpaca/skill", - "version": "0.8.0", + "version": "0.7.3", "private": true, "description": "designpaca 스킬 원본 — SKILL.md 와 참조 문서", "scripts": { diff --git a/packages/skill/references/audit-gate.md b/packages/skill/references/audit-gate.md index 9f75103..5a58182 100644 --- a/packages/skill/references/audit-gate.md +++ b/packages/skill/references/audit-gate.md @@ -29,9 +29,6 @@ | 같은 명시도의 숨김 규칙이 기본 규칙보다 앞에 있어 짐 — 모바일에서 숨기려던 kbd 안내가 렌더됨(6% 시각 diff) | 시각 회귀 + 규칙: **숨김 오버라이드는 기본 규칙보다 파일 뒤쪽에** 두거나 컴포넌트 규칙 근처의 미디어쿼리로 | | 탐색 차터가 결과를 단언하지 않아 "9288점" 입력이 조용히 통과(값을 덧붙인 타이핑) | 하니스 규칙 8(검사는 단언)·9(number 입력 조작) | | 모바일 라운드 — 중앙 모달이 썸존 밖, 백 키가 앱을 통째로 종료, safe area 무시, 입력 확대 | L6 시나리오(백·시트·터치타깃 스윕) + `references/mobile-app-ux.md` 표준 — **OS 관습은 조사 없이 바꾸지 않는다** | -| 탭 전환 스크롤 잔류 — 짧은 뷰에서 클램프되어 헤더 반쯤 잘림, 뷰마다 크롬 위치 제각각 | 뷰 전환 `scrollTo(0,0)` 단언(M5) + mobile-app-ux.md | -| 크럼을 헤더로 내보내 빈약한 헤더 — 계측 전부 통과했지만 사용자에게 '깨져 보인다' | 헤더 sticky·브랜드+액션 구조 단언 + **시각 무게 평가(눈)** — mobile-app-ux.md | -| 왼쪽 인셋 리듬 불일치(제목 16 vs 앱바·카드 24) — 오버플로 검사는 오른쪽만 보므로 통과 | M6 왼쪽 인셋 스위트(텍스트 시작점 ≥12px·같은 축 ±10px) + preflight §0-E | ## 7계층 @@ -86,9 +83,6 @@ L1·L5 는 프로젝트 안에 `tools/unit/*.test.mjs`, `tools/exploratory.mjs` 12. **페이지→노드 직렬화는 NaN 방어** — `getComputedStyle(el).fontSize` 등을 문자열 보간하면 "16px"이 NaN이 되고, 하니스는 NaN을 `null`로 받아 `"////px"` 같은 고장난 상세를 리포트한다. 수치는 항상 `parseFloat(x) || 0` 로 감싼다. 13. **`position: fixed` 요소의 보임 판정은 offsetParent 로 하지 마라** — fixed 는 offsetParent 가 `null`이다. display 계산값·높이로 판정한다. 14. **실기기(에뮬레이터) 검증은 텍스트 근거로** — `uiautomator dump` 의 `text="…"`에서 화면 제목·활성 탭을 읽어 단언한다. 스크린샷+비전은 "백 직후 전환 중 프레임"을 잡는 타이밍 오탐이 있다(실측 사고). 물리 폰이 붙어 있으면 `adb -s ` 로 대상을 한정한다. -15. **시각 기준 갱신은 의도 변경 리뷰를 동반한다** — "달라졌으니 갱신"으로 결함이 기준에 박힌다(실측: 왼쪽 인셋 결함 화면이 기준 갱신으로 '정상'이 되어 재발을 숨겼다). 갱신 전에 무엇이 의도적으로 바뀌었는지 한 줄씩 나열하고, 사용자 보고가 있던 화면은 수리 후 재촬영한다. -16. **사용자 보고 스크린샷은 비전 요약이 아니라 픽셀 판정으로 해부한다** — 비전 모델의 픽셀 추정("~0px", "~16px")은 부정확하다. pngjs 등으로 화면을 가로 밴드로 나눠 각 밴드의 텍스트 시작 x를 직접 재면(보임 행만, 왼쪽 테두리 아티팩트 제외) "무엇이 어디서 어긋났는지"가 수치로 나온다. 실측: '제목이 가장자리에 붙는다'는 보고가 앱바 24px·제목 16px의 리듬 불일치로 확정됐다. -17. **새 검사는 수정 전 코드로 RED 재현 후 GREEN** — 검사 자체의 측정 하자(오탐)도 이렇게 잡는다. 실측: 왼쪽 인셋 검사 초안이 ①`display:none` 요소의 rect=0, ②전폭 컨테이너의 보더박스(텍스트가 아님)로 38개 오탐 — RED 단계에서 발견해 교정했다. ## 리포트 양식 diff --git a/packages/skill/references/preflight.md b/packages/skill/references/preflight.md index 521d9fa..0e43763 100644 --- a/packages/skill/references/preflight.md +++ b/packages/skill/references/preflight.md @@ -42,12 +42,6 @@ canvas { display: none !important; } - 토큰 파일이 있는데 다른 CSS 파일에 색 리터럴(hex·rgb)이 남아 있으면 실패다. 반지름 리터럴도 마찬가지 — 눈 검사로는 절대 전부 못 잡는다(실측: 8건의 상수색이 눈 검사를 통과했다) - `theme-color` 메타가 실제 배경색과 다르면 **낡은 토큰**이다. 프리셋 전환에서 브랜드 표면(theme-color·파비콘·og:image)이 누락됐다는 뜻이다 -### E. 왼쪽 인셋 리듬 (모바일) -오버플로 검사는 오른쪽만 본다 — **왼쪽이 붙어 깨지는 결함은 통과한다**(실측: 제목 16px·앱바·카드 24px 혼재가 게이트를 통과해 사용자 보고로 나왔다). 모바일 폭(320·390)에서: -- 화면의 모든 보이는 텍스트 시작점 ≥ 12px -- 헤더 텍스트·화면 제목·카드 텍스트가 **같은 축**(±10px) -- 측정은 전폭 컨테이너의 보더박스가 아니라 **텍스트 시작점**, `display:none` 요소(rect=0)는 제외하고 - --- ## 1. 접근성 — 타협 없음 diff --git a/packages/skill/tools/design-gate.mjs b/packages/skill/tools/design-gate.mjs index 8f8ec0f..5f290ce 100644 --- a/packages/skill/tools/design-gate.mjs +++ b/packages/skill/tools/design-gate.mjs @@ -31,8 +31,8 @@ const DEFAULT_CFG = { ], widths: [320, 375, 390, 768, 1024, 1440], fontScales: [1, 1.3], - thresholds: { contrastNormal: 4.5, contrastLarge: 3, visualDiffPct: 0.1, stackLines: 3, radiusLiterals: 3, tapTargetMin: 44, inlineTargetMin: 24, leftInsetMin: 12 }, - checks: { meta: true, contrast: true, stack: true, wrap: true, rhythm: true, scaleMatrix: true, tapTargets: true, leftInset: true, static: false, visual: false, webkit: false, deadCss: true, cssHygiene: true }, + thresholds: { contrastNormal: 4.5, contrastLarge: 3, visualDiffPct: 0.1, stackLines: 3, radiusLiterals: 3, tapTargetMin: 44, inlineTargetMin: 24 }, + checks: { meta: true, contrast: true, stack: true, wrap: true, rhythm: true, scaleMatrix: true, tapTargets: true, static: false, visual: false, webkit: false, deadCss: true, cssHygiene: true }, deadCssIgnore: ["is-"], // 동적으로 조립되는 클래스 접두사(is-${type} 등). 오탐 방지 — 프로젝트마다 추가한다 chromePath: null, }; @@ -382,12 +382,7 @@ for (const p of CFG.pages) { await page.goto(href, { waitUntil: "networkidle0" }); await page.evaluate(UTILS); if (p.brand) { - // 동일 셀렉터가 여러 개(사이드바+탑바 등)면 보이는 것을 잰다 — display:none 의 rect 는 0 - const bl = await page.evaluate((sel) => { - const els = [...document.querySelectorAll(sel)]; - const el = els.find((e) => e.offsetParent !== null || getComputedStyle(e).position === "fixed") || els[0]; - return window.__lines(el); - }, p.brand); + const bl = await page.evaluate(`__lines(${JSON.stringify(p.brand)})`); bl === 1 ? pass(`${p.name} 브랜드 1줄`) : fail(`${p.name} 브랜드 ${bl}줄(수축 의심)`); } } @@ -420,40 +415,6 @@ for (const p of CFG.pages) { bad.length ? fail(`${p.name}/${v} 터치타깃`, bad.join(" · ")) : pass(`${p.name}/${v} 터치타깃`, `컨트롤 ≥${CFG.thresholds.tapTargetMin}px·인라인 ≥${CFG.thresholds.inlineTargetMin}px`); } } else skip("터치타깃", "checks.tapTargets=false"); - - // ── 왼쪽 인셋(모바일) — 오버플로 검사는 오른쪽만 본다. - // 실측 사고: 제목 16px vs 앱바·카드 24px 혼재가 모든 게이트를 통과했고 - // 사용자에게 '가장자리에 붙어 깨졌다'고 보고됐다. 텍스트 노드 Range 로 글자 시작점을 잰다 - // (전폭 컨테이너의 보더박스가 아니라. display:none 의 rect=0 은 보임 검사로 제외). ── - if (CFG.checks.leftInset) { - await page.setViewport({ width: 390, height: 844, isMobile: true, hasTouch: true }); - await page.goto(href, { waitUntil: "networkidle0" }); - for (const v of p.views || ["single"]) { - await goView(v); - const inset = await page.evaluate(() => { - let min = null; - const walk = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); - let n; - while ((n = walk.nextNode())) { - if (!n.textContent.trim() || !n.parentElement) continue; - const cs = getComputedStyle(n.parentElement); - if (cs.display === "none" || cs.visibility === "hidden" || cs.position === "fixed") continue; - const range = document.createRange(); - range.selectNodeContents(n); - const r = range.getBoundingClientRect(); - if (r.width < 2 || r.height < 4 || r.left < 0) continue; - const l = Math.round(r.left); - if (min === null || l < min) min = l; - } - return min; - }); - inset === null - ? skip(`${p.name}/${v} 왼쪽 인셋`, "텍스트 없음") - : inset >= CFG.thresholds.leftInsetMin - ? pass(`${p.name}/${v} 왼쪽 인셋`, `최소 텍스트 시작 ${inset}px`) - : fail(`${p.name}/${v} 왼쪽 인셋`, `텍스트가 ${inset}px에서 시작 — 기준 ≥${CFG.thresholds.leftInsetMin}px(가장자리에 붙어 보인다)`); - } - } else skip("왼쪽 인셋", "checks.leftInset=false"); } // ── L3 시각 회귀 (옵션: pixelmatch·pngjs 설치 시) ──