diff --git a/apps/site/design.md b/apps/site/design.md index 9cde6d9..0f7ba59 100644 --- a/apps/site/design.md +++ b/apps/site/design.md @@ -790,3 +790,40 @@ max-age=0 지정(존 설정이 우선하면 효과는 제한적). **운칙: /wor - 사용자 보고 "왼쪽 타임 라벨 깨짐" — 계측으로 확정: 라벨 top 간격이 22·194·43·22 불규칙, 일부 라벨 폭 78px(요일 열에 배치). **원인: span-2 셀이 섞인 그리드 자동 배치(auto-placement)의 취약성** — 시간 라벨(1행)과 셀(2행 스팬)이 교차하며 희소 배치 커서가 열1의 빈 행을 잡아 라벨이 엉뚱한 행·열로 흩어졌다. 세션 초반 의심 목록에 있던 "--rows 미사용 문제"의 실체이기도 하다. - 수리: buildAxis 를 **전 요소 명시적 격자 좌표**(corner 1/1, day 1/(d+1), hour (h-9)*2+2행/1열, cell 행 span2/열 d+1)로 재작성 — 자동 배치 의존 제거. 검증: 9라벨 전부 1열(40px 균일), 간격 Δ43~44px 등차(2행×21.6px 설계 정합). 시각 diff 0.38%(라벨 재배치 = 의도) 확인 후 기준 갱신, 게이트 통과, v18 배포. - **교훈: 혼합 span 의 그리드는 자동 배치를 믿지 않는다** — 좌표를 명시하라. 격자 레이아웃 검사에는 '라벨 등간격(Δ 균일)' 불변식을 넣을 가치가 있다(스킬 하니스 규칙 후보). + +## 2026-08-22 — 프로덕션 마감(v18/v19): 상태 완결성 · 인쇄 · 단축키 · 브랜드 정합 + +디자인 스킬 13종 전량 병행 — designpaca 연장 경로(토큰·컨셉 승계) + redesign-existing-projects 감사 우선. taste 계열(안티슬롭·하이엔드·GSAP·미니멀·브루탈리스트·스티치)은 관리 콘솔에 적용되는 규칙만(상태 완결·대비·모션 문법·카드 절제·메타라벨 금지), 이미지젠 계열 4종은 구성·계층·가독성 기준으로 감사에 반영(관리 콘솔에 사진 소재는 부적합 — 생성 도구도 이 하니스에 없음). + +- **브랜드 낡은 값 교정**: theme-color 가 종이 시대 값(#F7F5EF/#FBFAF7), 파비콘이 v7 프리셋 전환 이전 문양(형광펜/남색)이었다. #fafafa + 현 브랜드 마크(가온=에메랄드 '가', 두레=벽돌 申)로 교체, og.png 재캡처. +- **상태 완결성**: 가온 채점 입력에 범위 검증(GAON.validateScore — 0–100 밖이면 인라인 경고 + 저장 차단 + aria-invalid, 평균은 유효값만 계산). 두레 취소에 실행취소(상태줄 안에 되돌리기 버튼 — 재신청은 canEnroll 로 재심사, 사이에 다른 신청이 끼면 정당하게 막힘). 알림 센터 Esc 닫힘. 로딩 상태는 의도적으로 만들지 않았다 — 동기 인라인 데이터에 스켈레톤은 '저장을 흉내내지 않는다' 원칙 위반. +- **도메인 기능 — 인쇄**: @media print 2종. 가온은 활성 뷰를 서류로(모달이 열려 있으면 body:has(dialog[open]) 로 앱을 숨기고 그 서류만), 두레는 A4 가로 시간표 + 강의계획서. print-color-adjust: exact 로 상태색 보존. 탑바/앱바에 인쇄 버튼. +- **키보드 단축키**: 숫자키 뷰 전환(가온 1–9, 두레 1–6), / 검색 포커스(입력 요소 가드). kbd 물리 키 칩 안내 — 가온 사이드 하단, 두레 앱바(모바일 숨김). +- **모션 문법 토큰화**: --ease-out(0.16,1,0.3,1)·--dur-1/2/3 한 곡선 세 지속시간. 뷰 전환 70ms 상승 — 시각회귀 스크린샷 대기(100ms) 안에 종료시켜 흔들림 없음. prefers-reduced-motion 전역 블랭킷(무한 루프·전환 정지, 기능은 유지). +- **토큰 위생**: 지각 상태쌍(--late/--late-soft), --primary-hover, --accent-strong, --on-accent 를 tokens.css 로 이동 — main.css 의 상수색 제거. radius 를 5종 규모(--radius-xs/6/--radius-md/12/pill)로 정리, 난립하던 2/3/4/8/999px 리터럴 종식. +- **죽은 코드 제거**: 가온 .page-head·.btn-primary/outline/ghost·.hl-count·.stamp(HTML 3곳·CSS), 두레 .modal-title/.modal-meta/.modal-inner/.facts(+모바일 쿼리 잔여), 두레 renderCreditsView 이중 계산 블록(첫 블록이 즉시 덮임), conflictsWith(canEnroll 로 흡수). +- **calc.js 확장**: GAON.validateScore, DURE.canEnroll(정원·겹침·학점 한 번에 판정 — 모달 경고 문장과 실행취소 재심사가 같은 순수 함수 공유). 단위 테스트 30→41건. +- **감사 도구 확장(도구도 제품)**: audit.mjs +24항목(채점 차단/해제·실행취소 복원·단축키 4건·인쇄 CSS 존재). exploratory 차터1 을 실제 완주로 고침 — type("88") 이 기존 값에 덧붙어 "9288"이 되던 조용한 테스트 결함(검증 추가로 처음 드러남; number 입력은 선택 덮어쓰기 불가 — 비우고 타이핑). +- **사고 2건, 게이트가 잡음**: ①두레 320px +18px — 인쇄 버튼이 앱바 최소폭 합을 넘김 → ≤680px 액션 줄 2행 랩. ②가온 390px 6% 시각 diff — .side-kbd 기본 display:flex 가 파일 뒤쪽에 있어 앞쪽 900px 미디어쿼리의 display:none 을 캐스케이드로 이김 → 숨김 오버라이드는 기본 규칙 **보다 뒤에** 두어야 한다. 캐시 운칙 준수: main.css v18(가온)/v19(두레), calc.js?v=2. +- 쇼케이스 -2컷(모달 열림) 재캡처 — capture-work-shots.mjs 신설(CLI 크롬 --screenshot 은 모달을 못 열어 puppeteer 가 담당). 게이트 전 계층 통과(L0 2종·L1 41·L2 72+10·L3 30·L4 8·L5 14). + +## 2026-08-22 — 스킬 내재화(v20): 이번 사고를 스킬 규칙으로 승격 + +프로덕션 마감 라운드에서 게이트가 *사후에* 잡은 결함 11종을 "스킬이 처음부터 조였다면 원천 차단됐을 규칙"으로 도출해 designpaca 스킬에 박았다(packages/skill — 하드게이트 13·14, 4-5 서류 기능 단계, preflight 상태 완결성 §4-1, 하니스 규칙 8~11, design-gate 신규 3검사). 이 앱들은 그 도그푸드 대상이다. +- **게이트가 수작업 스윕을 이겼다**: 새 deadCss 검사가 내가 직접 정리하고도 놓친 `.rep-stat-sub`·`:not(.page-head)` 잔여(모바일 쿼리)를, cssHygiene 이 `.bell-head button`의 `border-radius: 4px` 리터럴을 각각 잡음. "눈 검사로는 절대 전부 못 잡는다"가 이 라운드의 입증이다. 세 건 정리. +- 색 리터럴 잔여 전량 토큰화: 표지판 위 잉크(--panel-muted/faint/line/mark)·백드롭(--backdrop-dim)·인쇄 매체(--print-line/paper)·두레 평가 농도(--accent-tint-2/3) — main.css 색·radius 리터럴 0건(양 앱, 게이트 실측). +- 검증: 스킬 게이트 신규 검사 도그푸드 양방향 — 고의 결함 픽스처(죽은 클래스·토큰 밖 색·theme-color 불일치)는 3검사 정확히 FAIL, 실제 두 앱은 전 PASS. 리포 게이트 L0~L5 재통과. CSS v20(가온). + +## 2026-08-23 — 모바일 앱 수준 UX(v21/v22): HIG·M3 표준 이행 · L6 시나리오 E2E · 실기기 검증 + +13종 디자인 스킬 재동원(designpaca 연장 + redesign 감사 우선). 조사를 먼저 — Apple HIG(탭바 5~6·시트·safe area), Material 3(바텀내비 3~5·48dp), NN/g(바텀 시트·터치타깃), CSS-Tricks(iOS 16px 확대)를 근거로 Nielsen 10 휴리스틱 감사로 위반 도출(백 키 앱 종료·중앙 모달 썸존 밖·safe area 무시·입력 확대 유발). 조사·계획: research/mobile-app-ux-v1.md. + +- **앱 탐색 구조**: 바텀 내비 5탭(가온 홈/수강생/출석부/성적/더보기, 두레 과목 찾기/관심/신청 내역/이수/더보기) + 더보기 바텀 시트(그룹 헤더 — 가온 학적/수업/학사 9항목, 두레 6항목). ≤900px 사이드내비/탭 전환. +- **하드웨어 뒤로가기 = 앱 내 히스토리**: 뷰 전환 pushState, 다이얼로그도 같은 스택(suppressPop/popClosing 상호 무시 플래그, 더보기 선택은 pendingMoreSelect 로 back+push 접합). 백 키가 앱을 종료하지 않는다. +- **safe area·입력·터치**: viewport-fit=cover + env(safe-area-inset-*) max() 패딩, ≤680px 입력 16px(iOS 확대 방지 — maximum-scale=1 은 WCAG 위반으로 안 씀), coarse 44px 타깃, tap-highlight·overscroll·touch-action 위생. +- **바텀 시트**: ≤680px 모달이 하단 시트로(그래버 36×4, 92dvh, sheet-up 모션), 인쇄에서 모바일 크롬 제거. +- **PWA**: manifest.webmanifest + sharp 생성 아이콘 192/512(가온=장부 문항 에메랄드, 두레=시간표 블록 벽돌) + apple-touch-icon·모바일 메타 2종. +- **TDD RED→GREEN**: tools/e2e.mjs 신설 — 앱 기반·320/390/768 매트릭스·터치타깃 231·대비 1,120·백 키·시트·더보기·강사 하루 장기 여정(S1 25단언)·정정기간 여정(S2 27). RED 1,742 단언 318 실패(신규 기능에 정확히 집중) → GREEN 1,762/0. verify 체인 L6 등록, 전 계층 재통과. +- **실기기 검증(안드로이드 에뮬레이터·실제 Chrome)**: adb reverse + uiautomator dump 텍스트 근거로 — 가온 렌더·수강생 탭·**백 키→대시보드 복귀**·더보기 시트·공지 선택·백 복귀, 두레 렌더·관심 탭·백 복귀. iOS 는 WebKit 엔진(L4)으로 대체(Windows 제약 문서화). 비전은 전환 중 프레임 오탐이 있어 '여부' 판정은 계측에만. +- **도그푸드가 잡은 사고**: ①스킬 게이트 새 tapTargets 검사 개발 중 thresholds 얕은 병합 버그(구형 설정이 신규 기본값 지움, "≥undefinedpx") → 깊은 병합. ②죽은 선택자 검사가 이번 라운드 내가 만든 `.btn-seal/.btn-star`(가온에 없는 버튼)과 두레 포크 잔재 6종을 적발 → 삼위일체 제거, 세그먼트 `seg-${n}` JS 조립은 무시 목록(seg-)으로 오탐 차단. ③두레 `.btn-seal`은 생존(신청 버튼) — coarse 사이징 실수로 뺐다가 게이트 재실행으로 복원. CSS v22(양앱). +- **스킬 내재화(v0.7 예정)**: references/mobile-app-ux.md 신규(5축 표·뒤로가기 전체 패턴·실기기 검증법·출처), audit-gate 7계층(L6 시나리오)·하니스 12~14(NaN 방어·fixed 판정·실기기 텍스트 근거), design-gate tapTargets 내장, SKILL.md 규칙 7 "OS 관습은 조사 없이 바꾸지 않는다". diff --git a/apps/site/gate-report.md b/apps/site/gate-report.md index b351de0..bd4f2f9 100644 --- a/apps/site/gate-report.md +++ b/apps/site/gate-report.md @@ -1,9 +1,13 @@ -# 게이트 리포트 — 2026-08-22 12:20 +# 게이트 리포트 — 2026-08-23 04:48 **전 항목 통과 — 배포 가능** ``` PASS L0 정적 — SKIP — checks.static=false +PASS gaon-lms 죽은 선택자 — 2개 CSS 전 클래스 사용 중(무시 2접두사) +PASS dure-enrollment 죽은 선택자 — 2개 CSS 전 클래스 사용 중(무시 2접두사) +PASS gaon-lms 토큰 위생 — 색 0·radius 0 리터럴(토큰 1파일) +PASS dure-enrollment 토큰 위생 — 색 0·radius 0 리터럴(토큰 1파일) PASS gaon-lms SEO/meta — title 18자·desc 74자·OG✓·alt 0결 PASS gaon-lms/dashboard 오버플로 — 320/375/390/768/1440 PASS gaon-lms/dashboard h1 1줄 @@ -52,6 +56,15 @@ PASS gaon-lms/notice 리듬·트랙 PASS gaon-lms/notice 대비 AA PASS gaon-lms 스케일×폭 h1 — scales 1/1.3 PASS gaon-lms 브랜드 1줄 +PASS gaon-lms/dashboard 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px +PASS gaon-lms/students 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px +PASS gaon-lms/attendance 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px +PASS gaon-lms/grades 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px +PASS gaon-lms/files 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px +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 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줄 @@ -85,6 +98,12 @@ PASS dure-enrollment/notices 리듬·트랙 PASS dure-enrollment/notices 대비 AA PASS dure-enrollment 스케일×폭 h1 — scales 1/1.3 PASS dure-enrollment 브랜드 1줄 +PASS dure-enrollment/catalog 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px +PASS dure-enrollment/starred 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px +PASS dure-enrollment/enrollments 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px +PASS dure-enrollment/credits 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px +PASS dure-enrollment/archive 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px +PASS dure-enrollment/notices 터치타깃 — 컨트롤 ≥44px·인라인 ≥24px PASS L3 시각 회귀 — SKIP — checks.visual=false PASS L4 WebKit — SKIP — checks.webkit=false ``` diff --git a/apps/site/gate.config.json b/apps/site/gate.config.json index 03362ed..65f8b75 100644 --- a/apps/site/gate.config.json +++ b/apps/site/gate.config.json @@ -61,5 +61,13 @@ "visual": false, "webkit": false }, - "chromePath": null + "chromePath": null, + "deadCssIgnore": [ + "is-", + "seg-" + ], + "tapTargetsInline": [ + ".row-open", + ".undo-btn" + ] } diff --git a/apps/site/public/work/dure-enrollment/calc.js b/apps/site/public/work/dure-enrollment/calc.js index 61a26fd..1bd883d 100644 --- a/apps/site/public/work/dure-enrollment/calc.js +++ b/apps/site/public/work/dure-enrollment/calc.js @@ -24,7 +24,18 @@ // 이수 잔여 — 음수면 '초과' const remaining = (need, done, now) => need - done - now; - const api = { isOverlap, credits, wouldExceed, seatsState, remaining }; + // 신청 가능 판정 — 정원·겹침·학점을 한 번에. 모달의 경고 문장과 실행취소 재심사가 같은 판정을 공유한다. + const canEnroll = (course, enrolledList, currentCredits, max) => { + if (!course || !course.seats) return { ok: false, reason: "full" }; + const names = (enrolledList || []) + .filter((o) => isOverlap(course, o)) + .map((o) => o.name); + if (names.length) return { ok: false, reason: "overlap", names }; + if (wouldExceed(currentCredits, course.credits, max)) return { ok: false, reason: "credits" }; + return { ok: true }; + }; + + const api = { isOverlap, credits, wouldExceed, seatsState, remaining, canEnroll }; global.DURE = api; if (typeof module !== "undefined") module.exports = api; })(typeof window !== "undefined" ? window : globalThis); diff --git a/apps/site/public/work/dure-enrollment/icon-192.png b/apps/site/public/work/dure-enrollment/icon-192.png new file mode 100644 index 0000000..aeebe2e Binary files /dev/null and b/apps/site/public/work/dure-enrollment/icon-192.png differ diff --git a/apps/site/public/work/dure-enrollment/icon-512.png b/apps/site/public/work/dure-enrollment/icon-512.png new file mode 100644 index 0000000..e304382 Binary files /dev/null and b/apps/site/public/work/dure-enrollment/icon-512.png differ diff --git a/apps/site/public/work/dure-enrollment/index.html b/apps/site/public/work/dure-enrollment/index.html index 2e7be71..1d6ad0a 100644 --- a/apps/site/public/work/dure-enrollment/index.html +++ b/apps/site/public/work/dure-enrollment/index.html @@ -2,8 +2,12 @@
- +- 신청 학점 0 / 18 -
+