diff --git a/docs/dev_dashboard.html b/docs/dev_dashboard.html index 7cf17ca..d150e1b 100644 --- a/docs/dev_dashboard.html +++ b/docs/dev_dashboard.html @@ -565,7 +565,7 @@
128차 적용(2026-07-03): 127차 후속 후보였던 live coach LLM audit 실패 은폐를 닫았다. record_llm_call_audit()가 DB pool 또는 audit.llm_call_log insert 실패로 False를 반환해도 기존에는 generate_live_coaching()이 이를 무시해 정상 status=ready AI 코칭처럼 보일 수 있었다. 이제 live coach는 audit hook의 False 또는 예외를 감사 저장 미확인으로 보고, LLM structured 응답을 버린 뒤 규칙 기반 status=degraded 코칭으로 내려간다. 상담 루프는 막지 않되 사용자에게 정상 AI 코칭처럼 보이지 않게 하는 조치다. 검증: C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m py_compile apps/api/app/services/live_coach.py apps/api/app/test_session_turn_persistence.py, C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider apps/api/app/test_session_turn_persistence.py -k "live_coach" -q 7 passed. 로컬 작업트리 기준이며 배포 전이다.
129차 적용(2026-07-13): 재부팅 뒤 Chrome이 기존 /admin 탭의 문서 스크롤을 복원하면서 sticky topbar/sidebar만 남고 관리자 본문은 화면 위로 밀리던 silent blank를 재현·수정했다. RED 재현은 길이 2200px 문서에서 scrollY=900을 만든 뒤 persisted pageshow를 보내 수정 전 scrollY=1300과 빈 main을 확인했다. App.tsx는 history.scrollRestoration=manual, pageshow reset, pathname layout-effect와 다음 frame reset을 함께 적용한다. 로컬 focused E2E는 desktop/mobile route 이동·복원 탭 4 passed, typecheck/build passed. Cloudflare Pages production 0daabb1b 배포 뒤 custom domain은 assets/index-BhyOF3rL.js를 서빙하며, 15분 일회성 관리자 세션으로 운영 홈·사용자·권한·티켓과 복원 탭을 공개 사이트에서 확인한 public-admin-visual 3 passed 후 세션을 즉시 삭제했다. 이후 concurrent avatar 변경이 섞인 배포는 Cloudflare Pages rollback API로 0daabb1b에 되돌렸다.
130차 적용(2026-07-13): 운영 /voice/health는 OpenAI 설정을 정상으로 보고했고 실제 운영 키 직접 smoke도 98,133-byte/11.68초 MP3를 반환했지만, 운영 로그에는 사용자의 voice WebSocket 연결이 없어 텍스트 턴 AI 응답이 TTS 경로에 연결되지 않은 공백으로 좁혔다. 인증된 POST /voice/speech는 session_id/turn_seq로 소유 회기와 이미 저장된 client-visible 내담자 응답을 다시 검증한 뒤 기존 voice map/OpenAI TTS로 MP3를 반환한다. Session은 텍스트 전송 user gesture에서 AudioContext를 먼저 unlock하고, 합성 Blob을 기존 Web Audio/Analyser 재생기로 보내며 새 턴이 시작되면 이전 요청을 무효화한다. 검증: backend voice 32 passed, API types check, web typecheck, voice-success text TTS+별도 mic 회기 UI focused 1 passed.
144차 적용(2026-07-27): 143차 전수 순회에서 소유자 결정 대기로 남긴 7건을 질문 도구로 전건 결정받아 6건 구현 완료(RED→GREEN, 5개 병렬 구현 에이전트). ① 설정 아바타 변경 — 계정 섹션에 온보딩과 같은 계약(3MB·PNG/JPEG/WebP)의 업로드 컨트롤+미리보기, PATCH /users/me avatar_url 저장 후 refresh로 톱바·레일까지 갱신. ② 동의 관리·철회 전체 흐름 — 새 #set-consent 섹션(실습 동의 시각·약관/개인정보 버전 표시), learner 철회 버튼(2단계 확인)→차단 안내→"다시 동의" 재동의, 세션 프리스타트의 기존 동의 게이트와 연결(철회 함정 상태 해소, dead code였던 withdrawConsent 실사용화), e2e는 고유 계정으로만 철회 수행. ③ 신규 학습자 기초 우선 추천 — 회기 0건 학습자의 기본 선택·첫 회기 준비 카드를 난도 오름차순 첫 페르소나로(starterPersona()), 기록 보유·조회 실패 시 기존 규칙 유지. ④ 학생 분석 테이블 가상화 — @tanstack/react-virtual(신규 의존성) 세로 가상화, 동적 행 높이 measureElement·펼침 재측정, 300행 fixture에서 렌더 행 <60 검증, 데드스페이스 없게 max-height는 내용 이하로. ⑤ 저작 모드 좌측 레일 마크업 정리 — 어떤 경로에서도 보이지 않던 죽은 DOM(ps-rail)과 전용 CSS 삭제, 특성화 테스트 toHaveCount(0) 강화. ⑥ 감정 밸런스 타임라인 차트 구현(143차 미구현 항목) — 혼합 접근: deep 평가 스키마에 turn_valence(seq·v −1~+1, 극단값 회피 지시) 신설 + 저장 데이터 결정론 파생 폴백(counselorBaseline=학습자 턴 rapport_signal 누적 이동평균, clientValence=deep 값 우선/client_state 극성+appropriateness 결합 폴백), 2점 미만 빈 배열·10점 초과 균등 리샘플·축 라벨 4개, hidden_turns 시 비산출로 프라이버시 계약 유지. dev-login 재승인은 현행 유지로 종결. 검증: 각 과제 RED 관측→GREEN(에이전트별 증거 로그), 백엔드 pytest 421 passed(신규 test_review_valence.py 9 포함), gateway 29, typecheck/build/design-ssot/dead-code/duplication(0.03%) 통과, focused 무회귀 44/42/40/20/14, professor·persona 레이아웃 게이트 통과, 감정 차트는 실DB 공감 5턴 세션에서 baseline·client 각 5포인트/축 4라벨 라이브 실측. 전 스위트 재완주 수치는 체크리스트 문서 §0에 기록. 부수 관찰: smoke-resistance-openness-db.py의 advice_jump turn_client_state ≥5 단언이 엔진 변동성에 민감(결함 로그 #23, 코드 회귀 아님). 체크리스트 415항목 미체크 0 도달. 로컬 작업트리 기준이며 배포 전이다.
144차 적용(2026-07-27): 143차 전수 순회에서 소유자 결정 대기로 남긴 7건을 질문 도구로 전건 결정받아 6건 구현 완료(RED→GREEN, 5개 병렬 구현 에이전트). ① 설정 아바타 변경 — 계정 섹션에 온보딩과 같은 계약(3MB·PNG/JPEG/WebP)의 업로드 컨트롤+미리보기, PATCH /users/me avatar_url 저장 후 refresh로 톱바·레일까지 갱신. ② 동의 관리·철회 전체 흐름 — 새 #set-consent 섹션(실습 동의 시각·약관/개인정보 버전 표시), learner 철회 버튼(2단계 확인)→차단 안내→"다시 동의" 재동의, 세션 프리스타트의 기존 동의 게이트와 연결(철회 함정 상태 해소, dead code였던 withdrawConsent 실사용화), e2e는 고유 계정으로만 철회 수행. ③ 신규 학습자 기초 우선 추천 — 회기 0건 학습자의 기본 선택·첫 회기 준비 카드를 난도 오름차순 첫 페르소나로(starterPersona()), 기록 보유·조회 실패 시 기존 규칙 유지. ④ 학생 분석 테이블 가상화 — @tanstack/react-virtual(신규 의존성) 세로 가상화, 동적 행 높이 measureElement·펼침 재측정, 300행 fixture에서 렌더 행 <60 검증, 데드스페이스 없게 max-height는 내용 이하로. ⑤ 저작 모드 좌측 레일 마크업 정리 — 어떤 경로에서도 보이지 않던 죽은 DOM(ps-rail)과 전용 CSS 삭제, 특성화 테스트 toHaveCount(0) 강화. ⑥ 감정 밸런스 타임라인 차트 구현(143차 미구현 항목) — 혼합 접근: deep 평가 스키마에 turn_valence(seq·v −1~+1, 극단값 회피 지시) 신설 + 저장 데이터 결정론 파생 폴백(counselorBaseline=학습자 턴 rapport_signal 누적 이동평균, clientValence=deep 값 우선/client_state 극성+appropriateness 결합 폴백), 2점 미만 빈 배열·10점 초과 균등 리샘플·축 라벨 4개, hidden_turns 시 비산출로 프라이버시 계약 유지. dev-login 재승인은 현행 유지로 종결. 검증: 각 과제 RED 관측→GREEN(에이전트별 증거 로그), 백엔드 pytest 421 passed(신규 test_review_valence.py 9 포함), gateway 29, typecheck/build/design-ssot/dead-code/duplication(0.03%) 통과, focused 무회귀 44/42/40/20/14, professor·persona 레이아웃 게이트 통과, 감정 차트는 실DB 공감 5턴 세션에서 baseline·client 각 5포인트/축 4라벨 라이브 실측. 전 스위트 재완주 수치는 체크리스트 문서 §0에 기록. 부수 관찰: smoke-resistance-openness-db.py의 advice_jump turn_client_state ≥5 단언이 엔진 변동성에 민감(결함 로그 #23, 코드 회귀 아님). 체크리스트 415항목 미체크 0 도달. 배포(2026-07-27): commit 4511383c로 커밋 후 Cloudflare Pages production d8ebcb61 배포 — 커스텀 도메인이 index-Dqr2stZ-.js 서빙, prod API 8001은 start-public-runtime.ps1로 새 코드 재기동, 공개 헬스·auth config(dev login disabled)·비인증 401·OAuth 302·watchdog 전체 healthy 실측.
144차 적용(2026-07-27): 브레이크포인트 경계 전수 스윕. 142차 접근성 감사가 1440·390 두 폭만 봤고 기존 layout-visual-gate도 7개 폭(390/720/861/900/1024/1280/1440)만 검사한다는 소유자 지적에서 출발했다. CSS 전체에서 고유 width 브레이크포인트 25개(340~1420)를 추출해 파일→페이지로 매핑하고, 페이지마다 자기에게 영향을 주는 브레이크포인트의 B-1 / B / B+1 과 실기기 대표 폭을 조합해(페이지당 17~36폭 × 높이 2종) 순회하며 요소 겹침 · 텍스트 잘림 · 폭 붕괴 · 화면 밖 이탈 · 문서 오버플로를 계산으로 검출했다. 1차 스윕에서 156개 문제 뷰포트 / 11종 결함이 나왔다. 수정한 결함: ① 설정 서브네비 라벨 전면 소멸(721~1080px 전 구간) — 공용 @media (max-width:1080px)의 .vg-set__rail{display:grid; grid-template-columns:minmax(220px,280px) minmax(0,1fr)}가 body[data-page="settings"]까지 덮어써 244px 레일 안이 다시 2열로 쪼개졌고 둘째 트랙이 0px로 붕괴, nav 폭 0 · nav-item 28px · 라벨 span 7개(계정/AI 운영/지원 요청/동의/테마/알림/음성) 폭 0이 됐다. 721~980px 는 별개로 .vg-set__nav-item span{display:none}이 라벨을 통째로 숨기고 있었다. 721~1080px 를 상단 가로 탭으로 통일해 라벨을 항상 노출한다. ② 세션 프리스타트 목표 fieldset 겹침(전 폭 72뷰포트) — .sx-prestart__note의 margin-top:-12px 음수 마진이 앞 형제 fieldset.sx-goals 마지막 줄을 88x12px 파고들었다. ③ 프리스타트 dd·desc 세로 잘림 — -webkit-line-clamp:2가 좁은 폭에서 문장을 잘라먹던 것을 해제하고, 데스크톱 한 화면 제약은 max-height + overflow-y:auto로 대체. ④ 프리스타트 우측 "진행 초점" 패널 195px 잘림(1041~1240px, 200개 폭) — 3열 minmax(220px,…) minmax(360px,…) minmax(260px,…)는 최소 888px를 요구하는데 이 구간의 실제 컨테이너는 735~886px뿐이라 트랙이 축소되지 못하고 우측 패널이 통째로 잘렸다. overflow-x:hidden이라 스크롤도 안 되고 문서 오버플로는 0이라 기존 "contained" 게이트가 구조적으로 못 잡던 유형이다. 트랙 최소치를 0으로 낮추고 2열 전환점을 1040→1260px로 올려 이중 안전망을 뒀다. ⑤ 티켓 필터 "초기화" 버튼 화면 밖 이탈(1180~1257px) — 7트랙 grid가 최소 954px를 요구하는데 셸 사이드바를 뺀 컨테이너가 876~950px라 마지막 트랙이 밀려났다. 4트랙 2줄로 낮추고 유연 트랙 최소치를 0으로. ⑥ 페르소나 이름 세로 잘림(320px) — 이름 칸이 150.6px뿐이라 3줄이 필요한데 line-clamp:2에 걸려 3번째 줄이 사라졌다. ≤420px 밴드에서 3줄 허용. 오탐으로 판정해 고치지 않은 것: .aic-ledger b·.lh-metric-card b의 2px 세로 초과는 line-height가 Pretendard content-area(ascent+descent≈1.09em)보다 작아 생기는 폰트메트릭 아티팩트다. overflow hidden/visible 6x DPR 캡처를 20개 폭에서 바이트 비교해 pixelDiff=0(잘린 픽셀 0)을 확인했고, 1180/1280px에서는 같은 렌더링인데 반올림 방향만 바뀌어 탐지에서 빠지는 것도 증거다. .vg-set__voice-info .vd(의도된 line-clamp:2)와 .vg-set__skel-sr(visually-hidden), .vg-avatar__stage(border-radius:50% 원형 마스크)도 오탐이다. 2차 스윕으로 1차가 못 본 사각지대(높이 브레이크포인트 620/820 경계, 탭 전환 등 인터랙션 후 상태, 와이드 폭 1600~2560, 초협소 320)를 14개 라우트에서 추가 순회했고 새 결함 0. 결과: 진짜 결함 0(잔여 66뷰포트는 위 오탐 3종). 검증: typecheck · check:design-ssot 통과, session.css raw color 216/216 유지, layout-visual-gate 15/15, session-layout+session-mvp+learner+settings 56/56, 앞선 병렬 수리 검증에서 session-layout+settings+admin 55/55. 미결(소유자 결정): 프리스타트에서 호소·대상 정보가 .sx-prestart__case(좌측 아바타 카드)와 .sx-prestart__facts(본문)에 중복 렌더된다(3열·2열 양쪽 모두, 이번 변경과 무관한 기존 IA 문제). 로컬 작업트리 기준이며 배포 전이다.
143차 적용(2026-07-27): 전 페이지·버튼 단위 전수 E2E 순회 캠페인. IA 전 라우트(로그인/온보딩/승인대기/학습자 3뷰·세션·리뷰/교수자 콘솔·분석·페르소나/관리자 5섹션/설정/아바타 랩)의 기능 412개를 소스 기준 인벤토리로 만들어 docs/ops/e2e-full-sweep-2026-07-27.md 체크리스트 백로그(- [ ]/- [x])로 관리하고, RED→GREEN으로 결함을 수정했다. 신규 스펙 10파일 e2e/full-sweep-*.spec.ts(수집 424의 절반가량)를 추가했고, 소유자 결정 대기 항목은 test.fixme + 백로그 B3-신규 6건으로 분리했다. 수정한 결함(RED 재현→GREEN): ① /admin/ai 부트 watchdog 오탐 오버레이(index.html 셀렉터에 .aic 추가), ② 한국어 PII 마스킹이 "유의점은·방치되는·마무리했을" 같은 일반 단어를 [NAME]으로 치환해 평가 문장을 훼손하던 과잉 마스킹(guardrail 꼬리 글자 가드), ③ 온보딩 연락처 무검증(서버 pydantic 검증+클라이언트 안내), ④ 리뷰 요약 조사 오류("하늘와의"→받침 인식 와/과)와 난도 원문 노출(easy→기초), ⑤ /pending 새로고침 무피드백+아이콘 오용(refresh 아이콘 신설), ⑥ 학습 대시보드 탭 패널 aria-label 고정, ⑦ 회기 종료 다이얼로그 반말 문체, ⑧ 설정 스크롤 스파이 마지막 섹션 미활성(0 임계값+밴드 최하단 규칙+바닥 폴백), ⑨ 모바일 세션 겹침 2건(타임바 :has 4행 템플릿, 코치 넛지 플로우 배치), ⑩ 관리자 콘솔 무한 렌더 루프 프리즈(TanStack autoResetPageIndex↔setState — users/services 메모화+autoResetPageIndex:false, 결정적 재현 스펙 2건을 회귀 가드로 유지), ⑪ 복수 코호트 조용한 유실(POST/PATCH /admin/users가 cohort_ids 첫 항목만 저장 → 콤마 직렬화+알림 SQL 배열 겹침 비교), ⑫ 공개 페르소나 보관 전면 불능(DELETE /personas/{id}가 실DB에서 항상 503 — archive_persona_family UPDATE…FROM RETURNING의 무한정 code 모호성 → card. 한정, 실API RED 503→GREEN archived 실측). 마무리 탐색 순회(실브라우저 6에이전트, 68항목)는 67 GREEN·감정 밸런스 차트 데이터 소스 미구현(서버가 항상 빈 배열 — 사문 렌더 경로, 로드맵 기록) 1건을 추가로 확인. 검증: 백엔드 pytest 412 passed(신규 회귀 테스트 8개 포함), gateway 29 passed, typecheck/build/check:api-types/design-ssot/dead-code/duplication(0.03%) 통과, layout-visual-gate 15/15, session-layout 8/8, 최종 npm run e2e 재완주 — 병렬 424 수집 그린(fixme 4·모바일 의도 숨김 2 스킵), 직렬 49/49 passed(14.4m), exit 0. 열린 항목: B3-신규 6건(설정 아바타 변경/동의 철회·재동의 흐름/첫 추천 난도/분석 페이지네이션/저작 레일 잔존 마크업/dev-login 재승인 정책), 공개 OAuth 스모크는 기존 B2 유지. 로컬 작업트리 기준이며 배포 전이다.
142차 적용(2026-07-27): design-taste-frontend 스킬의 보편 규칙(§4.5 대비 검사, §6.B/6.C 모션·다크모드, §9.F/9.G AI tell)을 기준으로 전 20개 라우트를 3역할·라이트/다크·데스크톱/모바일 54개 조합으로 순회 감사하고 결함을 수정했다. 감사는 눈대중이 아니라 페이지마다 WCAG 대비비를 계산하는 Playwright 스크립트로 측정했다. (A) 라이트 팔레트 AA 미달 — --ink-3(캡션·메타)가 모든 라이트 표면에서 2.7~3.1:1, --neutral-400(placeholder)이 2.4:1이었다. 색조를 유지한 채 #6a6c65/#6f6e68로 낮추고 위계 유지를 위해 --ink-2도 #4d514b로 조정했다(강도 13.9 · 본문 6.9 · 캡션 4.6:1). 다크는 --ink-3 #979a8f, --neutral-400 #949b8f. (B) 다크 토큰 이중 진실원본 — 다크 블록이 --text-muted 등을 var(--ink-3) 별칭 대신 값으로 하드코딩해 primitive 수정이 다크에 반영되지 않았다. 라이트와 동일하게 별칭으로 통일했다. (C) 채움색을 글자색으로 전용 — --accent는 채움 기준값이라 어두운 표면 위 본문 크기에서 4.3:1이다. --text-accent(= --accent-deep) 별칭을 신설해 kicker·secondary 버튼·역할 라벨 등 텍스트 용도를 옮겼다. (D) 로그인 안내 배너 1.19:1 — .lg-config가 다크 패널 전제(반투명 갈색 + 밝은 앰버)라 라이트에서 사실상 읽을 수 없었다. 테마별로 스왑되는 warn 의미 토큰으로 교체(5.3:1). (E) 미정의 CSS 변수 12종 — --text-dim·--good-solid·--border-faint·--fs-xl 등이 정의 없이 참조돼 색/배경이 조용히 무효화되고 있었고, learner-home의 --bg-inset/--border-soft 폴백은 다크 전제 값이 라이트 페이지에 적용되고 있었다. 전부 기존 의미 토큰으로 매핑했다. (F) 학습자 분석 테이블 컬럼 침범 — .pf-learner-row__id의 암시적 그리드 트랙이 자식의 width:max-content를 수용해 261px 셀이 288px로 늘어나 max-width:100%가 무력화됐고, 긴 이름 28행이 옆 컬럼을 27px 침범했다. minmax(0,1fr)로 고정해 0건. (G) 한글 조판 — word-break 기본값 때문에 "오전"이 "오/전", "검토 대기"가 "검/토 대기"로 쪼개졌다(5건). 세션 화면이 쓰던 keep-all + overflow-wrap:break-word를 전역으로 올려 0건. (H) 탭 타깃 — WCAG 2.2 SC 2.5.8(24px) 기준으로 약관 동의 라벨 19px→28px, 학습자 이름 버튼 19px→25px, 설정 range 슬라이더 16px→24px(투명 테두리 + background-clip:content-box로 시각 크기는 유지). (I) LCP — 렌더 블로킹 CDN 폰트에 preconnect/dns-prefetch 추가(Pretendard self-host는 별도 과제). 결과: 54개 조합 전부 대비 위반 0 · 탭 타깃 위반 0 · 라벨/alt 누락 0 · 가로 오버플로 0(감사 전: 대비 2,043건, 탭 타깃 355건). 검증: npm run typecheck, check:design-ssot 통과, layout-visual-gate 15/15, session-layout 8/8, auth-visual·teacher·admin·session-review·settings·learner 포함 E2E 87 passed + 14 passed. 미결(소유자 결정 필요): kicker의 accent dot이 기본 true라 회기 화면 한 곳에 11개 이상 찍히는 점(§9.F는 장식 dot 0을 요구, 다만 DESIGN_CONCEPT가 명시한 강조 장치라 임의 변경하지 않음), 영문 대문자 eyebrow(SPEND CONTROL·RUNTIME ROUTE·DAILY JOURNAL·SESSION·SUPERVISOR)의 한글화, 섹션당 eyebrow 밀도(§4.7은 섹션 3개당 1개), 중점(·) 라인당 다중 사용(/teach 33행), prefers-color-scheme 무시하고 다크 고정인 기본 테마(§8.C), 관리자 AI 비용 표시 정밀도($37.6560·$0.102885). 로컬 작업트리 기준이며 배포 전이다.
python -m compileall app engine_gatewaypy -3.11 -X utf8 scripts\check-phase3-artifacts.py --help + py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_dataset_export.py app/test_phase3_artifact_checker.py -qnpm run buildwrangler pages deploy dist --project-name vignette --branch main --commit-hash <HEAD> --commit-dirty=false9fa684e5, commit 3dfddcac; preview https://9fa684e5.vignette-b1q.pages.dev; custom domain serves assets/index-FVj73yvV.js/assets/index-B3HPjbqc.css.https://vignette.chanpaca.net/login?deploy=3dfddcacwrangler pages deploy dist --project-name vignette --branch main --commit-hash <HEAD> --commit-dirty=falsed8ebcb61, commit 4511383c(142~144차 반영); preview https://d8ebcb61.vignette-b1q.pages.dev; custom domain serves assets/index-Dqr2stZ-.js. (직전: 2026-07-15 9fa684e5/commit 3dfddcac.)https://vignette.chanpaca.net/login?deploy=4511383cindex-Dqr2stZ-.js). 백엔드는 start-public-runtime.ps1로 prod API 8001을 새 코드로 재기동 — 공개 API prod/db/engine 200, auth config 200(Google enabled, dev login disabled), 비인증 personas 401, Google OAuth start 302, watchdog 전체 healthy(engine·api·web-preview·cloudflared·public-api)./live2d/mao/* / /live2d/haru/* / /live2d/live2dcubismcore.min.jsdocker compose -f infra\docker-compose.yml config --quietrag.server sidecar removed.docker build -f apps/api/Dockerfile . / docker run ... python -c "import app.main" / docker build -f apps/web/Dockerfile apps/webnode_modules; API image excludes local .env files.