전수 E2E 순회·소유자 결정 6건 구현·디자인 감사 반영

- 전수 순회: IA 전 라우트 412개 기능 인벤토리를 체크리스트 백로그로 관리
  (docs/ops/e2e-full-sweep-2026-07-27.md), 신규 full-sweep 스펙 10파일 추가.
  RED→GREEN으로 결함 12건 수정: 관리자 무한 렌더 프리즈(TanStack autoReset
  루프), 복수 코호트 저장 유실, 페르소나 보관 503(SQL 컬럼 모호성), PII 과잉
  마스킹, /admin/ai watchdog 오탐 오버레이, 모바일 겹침 2건, 설정 스크롤
  스파이, 온보딩 전화번호 무검증, 리뷰 조사·난도 라벨, pending 피드백 등.
- 소유자 결정 구현: 설정 아바타 변경, 동의 철회·재동의 전체 흐름, 신규
  학습자 기초 우선 추천, 학생 분석 테이블 가상화(@tanstack/react-virtual),
  저작 모드 죽은 레일 정리, 감정 밸런스 타임라인 차트(deep turn_valence +
  결정론 파생 폴백).
- 디자인 감사(142차): 라이트 팔레트 AA 대비, 다크 토큰 별칭 통일, 미정의
  CSS 변수 정리, 한글 keep-all 전역화, 탭 타깃 24px, LCP preconnect.
- 검증: npm run e2e 병렬 432 수집 GREEN + 직렬 49/49 exit 0, 백엔드 pytest
  421, gateway 29, typecheck/build/design-ssot/dead-code/중복 게이트 통과,
  layout-visual-gate 15/15, session-layout 8/8. 상세는 SSOT 대시보드
  142~144차 노트.
This commit is contained in:
Yun Chan 2026-07-27 14:25:24 +09:00
parent 0ae94499f2
commit 4511383cd9
55 changed files with 9333 additions and 573 deletions

View file

@ -591,6 +591,55 @@
gap: 8px;
margin-top: 8px;
}
/* 업로드된 아바타 이미지 — 원형 프레임을 그대로 채운다(이니셜 폴백 대체). */
.vg-set__avatar img,
.vg-set__rail-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
display: block;
}
/* 아바타 업로드 컨트롤 — 숨은 file input + 버튼(온보딩과 같은 계약). */
.vg-set__avatar-actions {
position: relative;
margin-left: auto;
flex: none;
}
.vg-set__avatar-input {
position: absolute;
inline-size: 1px;
block-size: 1px;
opacity: 0;
pointer-events: none;
}
/* ── 동의 섹션 — 철회 확인·재동의 안내 블록 ── */
.vg-set__consent-block {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: var(--sp-3);
padding: var(--sp-4);
margin-top: var(--sp-3);
border: 1px solid var(--border-subtle);
border-radius: var(--radius);
background: var(--bg-surface-2);
font-size: var(--fs-sm);
color: var(--text-body);
line-height: 1.5;
}
.vg-set__consent-block > span {
min-width: 0;
flex: 1 1 240px;
overflow-wrap: anywhere;
}
.vg-set__consent-actions {
display: flex;
gap: var(--sp-2);
flex: none;
}
/* ── 모델/엔진 메타 한 줄(읽기전용 상태 표기) ── */
.vg-set__meta {
@ -1032,9 +1081,48 @@
.vg-set__range-control {
min-width: 0;
}
/* WCAG 2.2 SC 2.5.8(타깃 최소 24px): 브라우저 기본 range 높이 16px 미달이다.
thumb 24px 잡되 투명 테두리 + background-clip:content-box 실제로 칠하는
원은 16px 남긴다. 시각 크기는 그대로, 포인터 적중 영역만 24px 넓어진다. */
.vg-set__range-control input[type="range"] {
accent-color: var(--accent);
display: block;
height: 24px;
background: transparent;
-webkit-appearance: none;
appearance: none;
cursor: pointer;
}
.vg-set__range-control input[type="range"]::-webkit-slider-runnable-track {
height: 4px;
border-radius: 999px;
background: var(--border-strong);
}
.vg-set__range-control input[type="range"]::-moz-range-track {
height: 4px;
border-radius: 999px;
background: var(--border-strong);
}
.vg-set__range-control input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
box-sizing: border-box;
width: 24px;
height: 24px;
margin-top: -10px;
border: 4px solid transparent;
border-radius: 50%;
background: var(--accent);
background-clip: content-box;
}
.vg-set__range-control input[type="range"]::-moz-range-thumb {
box-sizing: border-box;
width: 24px;
height: 24px;
border: 4px solid transparent;
border-radius: 50%;
background: var(--accent);
background-clip: content-box;
}
.vg-set__meta {
margin-top: var(--sp-2);
@ -1281,15 +1369,18 @@ body[data-page="settings"] #set-support {
body[data-page="settings"] #set-engine {
order: 3;
}
body[data-page="settings"] #set-appearance {
body[data-page="settings"] #set-consent {
order: 4;
}
body[data-page="settings"] #set-voice {
body[data-page="settings"] #set-appearance {
order: 5;
}
body[data-page="settings"] #set-notify {
body[data-page="settings"] #set-voice {
order: 6;
}
body[data-page="settings"] #set-notify {
order: 7;
}
body[data-page="settings"] .vg-set__group {
padding: 18px;
border-radius: var(--radius);
@ -1404,48 +1495,92 @@ body[data-page="settings"] .vg-set__pill {
background: var(--glass-surface-inset);
}
@media (max-width: 980px) {
body[data-page="settings"] .vg-set {
grid-template-columns: 86px minmax(0, 1fr);
gap: 18px;
max-width: none;
/* 721~1080px 구간을 상단 가로 탭으로 접는다.
폭에서 좌측 레일 배치가 갈래로 깨져 서브네비 라벨 7개가 모두 0 됐다.
(1) 981~1080px: 공용 `@media (max-width:1080px) .vg-set__rail{display:grid;
grid-template-columns:minmax(220px,280px) minmax(0,1fr)}` 페이지 레일까지
덮어썼다. 레일 트랙은 244px 뿐이라 실측상 `220px 0px` 풀려 nav 0,
nav-item 28px 찌그러졌다(981·1023~1025·1079~1081 재현).
(2) 721~980px: 이전 980 미디어쿼리가 레일을 86px 아이콘 전용으로 만들며
`.vg-set__nav-item span{display:none}` 으로 라벨을 통째로 숨겼다.
경우 모두 라벨이 사라지므로, 프로젝트가 선호하는 가로 전환으로 통일한다.
244px+28px+820px = 1092px 요구하는 2열 배치는 1081px 이상에서만 쓴다. */
@media (max-width: 1080px) {
body[data-page="settings"] .vg-set__callout {
margin: var(--sp-4) 24px 0;
}
body[data-page="settings"] .vg-set {
display: block;
min-height: 100dvh;
max-width: none;
padding: 0 24px 28px;
}
/* 공용 1080 미디어쿼리의 2열 grid 무효화해 레일 전체를 가로 바로 되돌린다.
구간에서는 아래 카드와 같은 ·라운드로 맞춰 탭이 카드 밖으로 삐져나오지 않게 한다. */
body[data-page="settings"] .vg-set__rail {
padding: 18px 10px;
display: block;
position: sticky;
top: 0;
z-index: 20;
min-height: auto;
margin: 0 0 14px;
padding: 14px 16px 10px;
border-radius: var(--radius);
}
body[data-page="settings"] .vg-set__rail-title {
justify-content: center;
padding: 0 0 16px;
gap: 10px;
padding: 0 0 12px;
}
body[data-page="settings"] .vg-set__rail-title h1 {
font-size: 19px;
}
body[data-page="settings"] .vg-set__rail-title p {
display: none;
}
body[data-page="settings"] .vg-set__rail-mark {
width: 26px;
height: 26px;
}
body[data-page="settings"] .vg-set__rail-title div,
body[data-page="settings"] .vg-set__nav-item span,
body[data-page="settings"] .vg-set__rail-status {
display: none;
}
/* 탭이 많아 폭이 모자라면 가로 스크롤로 흘린다(라벨은 계속 노출). */
body[data-page="settings"] .vg-set__nav {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
gap: 6px;
overflow-x: auto;
padding: 0 0 2px;
scrollbar-width: thin;
}
body[data-page="settings"] .vg-set__nav-item {
justify-content: center;
min-height: 48px;
padding: 0;
flex: 0 0 auto;
min-height: 38px;
gap: 7px;
padding: 0 12px;
font-size: 13px;
white-space: nowrap;
}
body[data-page="settings"] .vg-set__nav-item span {
display: inline;
}
body[data-page="settings"] .vg-set__forms {
max-width: none;
}
}
/* 720px 1080 가로 배치를 그대로 이어받고 여백·타이포만 좁힌다.
display/position/overflow 같은 배치 선언은 1080 블록에 번만 두고 여기선 중복 제거. */
@media (max-width: 720px) {
body[data-page="settings"] .vg-set__callout {
margin: 12px 12px 0;
}
body[data-page="settings"] .vg-set {
display: block;
min-height: 100dvh;
padding: 0 16px 28px;
}
/* ≤720px 만 full-bleed 상단 바(모바일 헤더 패턴)로 되돌린다. */
body[data-page="settings"] .vg-set__rail {
position: sticky;
top: 0;
z-index: 20;
min-height: auto;
margin: 0 -16px 12px;
padding: 13px 16px 10px;
border-radius: 0;
@ -1456,44 +1591,27 @@ body[data-page="settings"] .vg-set__pill {
gap: 8px;
padding: 0 0 10px;
}
body[data-page="settings"] .vg-set__rail-title div {
display: block;
}
body[data-page="settings"] .vg-set__rail-title h1 {
font-size: 16px;
}
body[data-page="settings"] .vg-set__rail-title p {
display: none;
}
body[data-page="settings"] .vg-set__rail-mark {
width: 24px;
height: 24px;
}
body[data-page="settings"] .vg-set__nav {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
gap: 4px;
overflow-x: auto;
padding-bottom: 2px;
scrollbar-width: thin;
}
body[data-page="settings"] .vg-set__nav-item {
flex: 0 0 auto;
min-height: 34px;
gap: 5px;
padding: 0 7px;
font-size: 11px;
white-space: nowrap;
}
body[data-page="settings"] .vg-set__nav-item svg {
width: 14px;
height: 14px;
}
body[data-page="settings"] .vg-set__nav-item span {
display: inline;
}
body[data-page="settings"] .vg-set__forms {
gap: 10px;
}