전 저장소 리팩터링과 SSOT 정비
This commit is contained in:
parent
14ecbd4e7d
commit
3dfddcac6f
173 changed files with 19679 additions and 6952 deletions
|
|
@ -25,6 +25,8 @@ Vignette(AI 심리상담 시뮬레이션 훈련 플랫폼)의 시스템 아키
|
|||
자살 수단/방법 정보는 출력 가드레일이 차단한다.
|
||||
- **주입형(hook) 경계**: 오케스트레이터는 평가/로깅 함수를 *주입*받는다. `services/orchestrator.py`는
|
||||
`services/evaluator.py`를 import 하지 않는다(소유권 분리).
|
||||
- **LLM 감사 경계**: evaluator/orchestrator의 생성 시간·토큰·비용 기록은 `services/llm_audit.py`의
|
||||
`generate_with_audit()`가 소유한다. 생성 성공을 감사 저장 성공으로 오인하지 않고 호출자가 degraded 의미를 결정한다.
|
||||
- **degraded 폴백**: DB(단일 SoR)가 없어도 `app/store.py` in-memory 미러로 1턴이 돈다.
|
||||
|
||||
### 1.1 모노레포 레이아웃
|
||||
|
|
@ -254,8 +256,17 @@ RBAC×AIView로 차단된다. 이 모듈은 평가 신호만 산출한다.
|
|||
형태가 아니라 `quota`와 `credit_events`를 포함한 짧은 카드 계약이다. UI는 코칭 아바타 말풍선,
|
||||
근거 모달, 발화별 코칭 이력 오버레이로 표시한다.
|
||||
- 회기별 코칭 기회는 기본/최대 3개다. `POST /sessions/{id}/live-coach` 사용 시 `use/-1` 이벤트를 남기고,
|
||||
턴 평가가 `appropriateness=pos`, `rapport_signal>=0.35`, 그리고 단계 전환 또는 `effective_openness`
|
||||
상승을 동시에 만족하면 `recharge/+1` 이벤트로 1개를 재충전한다(최대 3개). 사용권 없음은 409로 막는다.
|
||||
충전은 2026-07-14 재설계된 게이트를 쓴다(`turn_runtime.should_recharge_live_coach_credit`):
|
||||
① 성과 충전 — `appropriateness=pos` + `rapport_signal>=0.15` + (단계 전환 또는 개방도 +0.01),
|
||||
또는 `neutral` + `rapport_signal>=0.35` + 개방도 상승 ② 페이싱 충전 — 평가 신호와 무관하게
|
||||
6턴마다 1개(평가 실패여도 충전, 순감 구조 방지). 사용권 없음은 409로 막는다.
|
||||
- 프론트 트리거: 코칭 화면(`feedbackMode=coached`)이 아닐 때 완료된 턴은 기회를 소모하지 않고
|
||||
`pendingCoachTurn`으로 대기하며, 컨트롤바 위 컨텍스추얼 넛지 칩("방금 발화에 코치 제안이 있어요")으로
|
||||
안내한다. 코칭 화면을 열면 대기 턴에 대한 코칭을 즉시 요청한다.
|
||||
- 코칭 프롬프트에는 이번 회기 목표(`goal_stages`)와 직전 코칭 2건(title/focus)을 주입해
|
||||
목표 정렬·조언 반복 방지를 유도하고, 규칙 폴백도 단계별 기본 다음 발화를 쓴다.
|
||||
- `record_llm_call_audit`는 dev degraded(무DB) 기동을 "기록 실패"로 보지 않는다
|
||||
(`runtime_fallback_allowed()`면 True). durable 환경의 실제 기록 실패만 코칭을 degraded로 강등한다.
|
||||
- 전달된 코칭과 충전/사용 기록은 `app.live_coach_events`에 저장된다. 원문 축어록을 중복 저장하지 않고
|
||||
PII 마스킹된 짧은 learner/client excerpt, 코칭 payload, `event_type/credit_delta/credit_balance/reason`만
|
||||
저장한다. DB 미가용 dev에서는 런타임 캐시로 폴백한다.
|
||||
|
|
@ -339,6 +350,14 @@ session lifecycle을 유지하며, future Node read API는 이 read-model contra
|
|||
기본값을 쓰되 학습자가 `humanistic`/`cbt`/`integrative` 중 하나를 명시 선택해 기존
|
||||
`theory_mode` 계약으로 보낸다. DB가 없으면
|
||||
`runtime_fallback_allowed()` 확인 후 `store.create(...)`로 in-proc 생성.
|
||||
2026-07-13 한신대 회의 P1 반영: 요청에 이번 회기 목표 단계 `goal_stages`(StageLabel 1~4개 —
|
||||
회의 권장은 2개 수준, 소유자 지시(2026-07-15)로 4개까지 허용. 중복 제거·최대 4 검증,
|
||||
`app.sessions.session_goals` JSONB 저장)를 받고, 응답에
|
||||
`started_at`/`goal_stages`/`duration_limit_seconds`/`warning_before_end_seconds`를 돌려준다.
|
||||
회기 종료는 단계 완수가 아니라 **시간 기반**이다 — 프론트가 60분 타이머·10분 전 알람·만료 시
|
||||
정리 유도를 주도하고, 서버는 제한+유예(`SESSION_DURATION_MINUTES`+`SESSION_OVERTIME_GRACE_MINUTES`)
|
||||
초과 시 신규 턴을 `409 session_time_over`로 거부한다(`_ensure_turn_time_allowed`, 음성 WS 동일).
|
||||
목표를 달성해도 시간 내에는 계속 진행한다(상태머신은 채점·표시용으로만 유지).
|
||||
- `POST /sessions/{id}/turn` — 동기 경로. `prepare_turn` → `run_turn_generate` →
|
||||
학습자 발화 turn(speaker=`counselor`, 원문 `text` + `text_masked`) + 내담자 응답 turn(speaker=`client`)
|
||||
순차 append → 상태 갱신. `EngineError`는 503으로 변환.
|
||||
|
|
@ -355,6 +374,14 @@ session lifecycle을 유지하며, future Node read API는 이 read-model contra
|
|||
임베딩 모델이 없으면 BM25-only degraded 색인으로 진행한다.
|
||||
- `POST /sessions/{id}/end` — `memory.make_carry_over(...)` → 세션 종료 + carry 준비 →
|
||||
`_schedule_session_evaluation(sess)`로 deep-loop 평가를 비동기 태스크로 발사.
|
||||
- 진행도 파생(P2, 2026-07-14): `session_read_model.build_session_progress(state, prev_rapport_credit,
|
||||
goal_stages)`가 상태머신 수치를 학습자-안전 %로 파생한다 — 단계별 누적 게이지(현재 단계는
|
||||
`rapport_credit / STAGE_ADVANCE_RAPPORT` 기준, 지나온 단계 100, 전이 대기 99 캡), 라포 누적
|
||||
(`/0.55` 전 주기 기준)과 이번 회기 증가분(`prev_rapport_credit` 대비), 방어(저항)·유효 개방도 %.
|
||||
노출 지점: `SessionDetailResponse.progress`, `TurnResponse.progress`, 스트림 `done` payload,
|
||||
음성 WS `reply`, 대시보드 `persona_progress[].rapport_percent`. rapport_credit이 회기 간 ×0.7
|
||||
이월되므로 게이지가 회기를 건너 누적된다(초심 상담자 학습 신호 — 회의 P2 의도). ideation·CCD는
|
||||
계속 비노출.
|
||||
- `GET /sessions/dashboard` — 학습자 본인 세션만 `include_turn_evaluation=true`로 집계해
|
||||
`overview/growth/persona_progress/achievements/recent_feedback`를 반환한다. `overview.archived_sessions`는
|
||||
학습자별 보관 상태(`app.session_archive_state`) 기준 카운트이며, 보관된 종료 회기는 리뷰 대기 행동 큐에서
|
||||
|
|
@ -494,8 +521,16 @@ HTTP error mapping을 유지한다.
|
|||
- `get_approved_persona(code)` / `list_approved_personas()` — 승인된 최신 버전 조회(AI 컨텍스트).
|
||||
- `get_catalog_persona(code)` — 승인 카드 조회, 실패 시 `settings.allow_seed_persona_fallback`이면
|
||||
`seed_fallback_persona`(degraded=True)로 폴백.
|
||||
- 페르소나 스튜디오: `/teach/personas`가 teacher/admin 전용 저작 작업면이다. 교수 콘솔은 진입점/triage만 맡고,
|
||||
실제 저작은 개요·임상·저항·회기·말투·안전·프롬프트 탭으로 분리한다.
|
||||
- 페르소나 워크스페이스: `/teach/personas`는 teacher/admin 전용 운영·저작 작업면이다. 상단 horizontal tab이
|
||||
`대시보드`(공개/활성 페르소나·학습 인원·세션·평가/라포·검수 요약), `카탈로그`(공개 규칙·구성·버전),
|
||||
`페르소나`(검색/필터 가능한 전체 table + 내부 `검수 현황` 탭)를 나눈다. 행을 누르면 소개·학습 현황·설정
|
||||
drilldown으로 들어가며, 수정/신규 작성은 URL query 상태의 독립 작업면에서 자료→초안→설정→검토 4단계를
|
||||
사용한다. 작성 중간 저장은 브라우저 임시 저장을 항상 남기고, 기본 식별자가 채워지면 기존 draft API에도 저장한다.
|
||||
구조화 편집 자체는 개요·임상·저항·회기·말투·안전·프롬프트 탭 계약을 유지한다.
|
||||
- 자유 양식 표 업로드(2026-07-13 회의 P4): `POST /personas/sources/upload`는 xlsx/xlsm/csv 파일을
|
||||
받아 `services/tabular_ingest.extract_tabular_text()`로 결정론 평탄화(라벨: 값 쌍, 시트 구분,
|
||||
cp949 폴백, .xls 거부)한 뒤 아래 `/personas/sources` 등록 경로를 그대로 재사용한다.
|
||||
업로드 원본 바이트는 핸들러 메모리에서만 파싱하고 저장하지 않는다(원본 파기 — hash-only 증거만 남음).
|
||||
- RAG 첨부 SSOT: `POST /personas/sources`는 첨부/붙여넣기 자료를 `mask_pii()` 후
|
||||
raw 원문 hash-only 증거는 `kb.raw_source_artifact`에 따로 기록하고, sanitized 파생본만
|
||||
`kb.source/document/chunk`에 evaluator 전용 근거(`visible_to=['evaluator']`, `sensitivity=2`)로 등록한다.
|
||||
|
|
@ -567,12 +602,16 @@ React 19 + Vite. 라우팅은 `apps/web/src/App.tsx`(react-router-dom).
|
|||
| `/teach/personas` | PersonaStudio(페르소나 저작·검수) | teacher/admin |
|
||||
| `/teach/session/:sessionId/review` | SessionReview(교수자 읽기 전용 회기 검토) | teacher/admin |
|
||||
| `/admin` | Admin(운영 홈) | admin |
|
||||
| `/admin/ai` | AdminAi(AI 엔진 설정, 기간별 DB 비용·토큰·계량 커버리지, provider/model 원장, 평가 캐시 효율) | admin |
|
||||
| `/admin/users` | Admin(사용자 관리) | admin |
|
||||
| `/admin/access` | Admin(접근 권한) | admin |
|
||||
| `/admin/tickets` | Admin(운영 티켓 처리 큐: 접수, 필터, 카테고리 큐, 우선순위, 수동 상태 변경 감사) | admin |
|
||||
| `/settings` | Settings | 3역할 공통 |
|
||||
|
||||
- `RequireAuth`가 `lib/auth`의 AuthContext로 가드하고, 권한 불일치 시 역할 홈(`roleHomePath`)으로 보낸다.
|
||||
- `App.tsx`는 모든 역할 페이지를 `lazy()`로 로드하고 공통 `Suspense` 부트 경계를 사용한다. 페이지별 순수 표시 계산은
|
||||
`pages/*/model.ts`, 브라우저 음성 캡처는 `pages/session/voiceCapture.ts`, 페르소나 이름·난도·아바타 팔레트는
|
||||
`lib/personaViewModel.ts`가 소유해 라우트 컴포넌트의 API/상태/렌더 책임과 분리한다.
|
||||
- API 클라이언트 `src/lib/api.ts`:
|
||||
- `apiFetch`/`api`(get/post/put/del) — 표준 JSON, 에러는 `ApiError`로 정규화, 항상 `credentials:"include"`.
|
||||
- SSE: `openSessionStream(sessionId, text, handlers)` — `fetch` 스트림을 직접 라인 파싱해
|
||||
|
|
@ -593,6 +632,8 @@ React 19 + Vite. 라우팅은 `apps/web/src/App.tsx`(react-router-dom).
|
|||
(예: `pages/login/login.css`, `pages/learner-home.css`, `pages/session/session.css`).
|
||||
TSX 안에 `<style>{..._CSS}</style>` template literal을 두지 않는다. 새 화면은 스타일을
|
||||
페이지/기능 CSS로 분리하고, 반복 JSX는 작은 presentational component로 빼서 인증·데이터 상태 로직과 섞지 않는다.
|
||||
- 공통 UI/셸 CSS의 raw color는 0개를 강제한다. 몰입형 세션·인증·아바타 아트처럼 전역 의미 토큰이 아닌 국소 색은
|
||||
`check:design-ssot`의 파일별 고정 예산을 넘길 수 없어 예외가 다른 화면으로 확산되지 않는다.
|
||||
- 로그인 진입 화면은 `pages/login/LoginBrand.tsx`, `LoginPanel.tsx`, `login.css`로 분리되어
|
||||
`Login.tsx`는 OAuth/dev-login 상태와 라우팅만 소유한다.
|
||||
|
||||
|
|
@ -728,8 +769,9 @@ DB 레벨 이중강제(`04_audit_eval_rls.sql` §5, `app/db.py` `acquire()`):
|
|||
- 프론트의 최초 진입 경로(`initialPathForUser`)는 pending이면 `/pending`, 온보딩 미완료 일반 사용자는
|
||||
`/onboarding`, 관리자 콘솔 접근권이 있는 사용자는 기본 역할이 learner/teacher여도 `/admin`을 우선한다.
|
||||
역할 전환용 `roleHomePath`는 그대로 역할별 홈(`/learn`, `/teach`, `/admin`)만 소유한다.
|
||||
- SPA 라우트 전환은 `App.tsx`가 `pathname` 변경마다 문서 스크롤을 맨 위로 복원한다. 긴 관리자 목록에서
|
||||
짧은 `/admin/access` 정책 화면으로 이동할 때 이전 `scrollY`가 유지되어 sticky 셸만 보이는 빈 화면을 막기 위한 계약이다.
|
||||
- SPA 라우트 전환은 `App.tsx`가 `pathname` 변경마다 문서 스크롤을 맨 위로 복원한다. 또한 브라우저
|
||||
`history.scrollRestoration`을 `manual`로 두고 `pageshow`에서도 다시 복원해, 재부팅 뒤 기존 관리자 탭을
|
||||
되살릴 때 이전 `scrollY` 때문에 sticky 셸만 보이고 본문이 화면 위로 밀리는 빈 화면을 막는다.
|
||||
- 관리자 콘솔은 shell만 남고 본문이 비는 silent failure를 허용하지 않는다. `/admin/users` 응답의
|
||||
`cohort_ids`, `active_sessions`, `created_at`와 `/admin/tickets` 응답의 `summary`, `by_status`,
|
||||
`by_priority` 같은 필드가 런타임 계약과 다르면 프론트가 안전한 기본값으로 정규화하고 `관리자 데이터 진단`
|
||||
|
|
|
|||
185
docs/guides/collaboration-for-researchers.md
Normal file
185
docs/guides/collaboration-for-researchers.md
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
# 연구팀·디자인 담당을 위한 협업 가이드 (Git을 처음 쓰는 분께)
|
||||
|
||||
이 문서는 **개발이 처음인 연구팀 대학원생·디자인 담당 학생**이 이 저장소(프로젝트 파일 보관소)에
|
||||
안전하게 참여하기 위한 안내서다. 어려운 용어는 나올 때마다 한 줄로 풀어서 설명한다.
|
||||
천천히 따라오면 되고, 막히면 언제든 단톡방에 물어보면 된다.
|
||||
|
||||
> **먼저 알아둘 3가지**
|
||||
> 1. 여러분은 코드를 몰라도 참여할 수 있다. 대부분은 **웹 브라우저 화면만으로** 가능하다.
|
||||
> 2. 실수해도 프로젝트가 망가지지 않도록 설계돼 있다. 아래 "하지 말아야 할 것"만 지키면 된다.
|
||||
> 3. 문구(화면에 보이는 글자) 수정은 **단톡방에 목록으로 전달하는 것이 가장 쉬운 기본 경로**다.
|
||||
> 직접 고쳐보고 싶은 분을 위한 방법도 뒤에 따로 안내한다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 왜 main(master)에 직접 손대면 안 되나
|
||||
|
||||
먼저 용어부터.
|
||||
|
||||
| 용어 | 한 줄 뜻 |
|
||||
|---|---|
|
||||
| **저장소(repository, 레포)** | 프로젝트의 모든 파일과 수정 이력이 담긴 온라인 보관소 |
|
||||
| **브랜치(branch, 가지)** | 원본을 건드리지 않고 따로 복사해서 작업하는 "작업용 사본" |
|
||||
| **main / master** | 실제 서비스에 반영되는 **원본 줄기**. 손대면 바로 모두에게 영향 |
|
||||
| **커밋(commit)** | "여기까지 이렇게 고쳤다"라고 저장하는 한 번의 기록 |
|
||||
|
||||
**비유로 이해하기.** `main(master)`은 병원의 **정식 환자 차트 원본**이라고 생각하면 된다.
|
||||
누구든 원본에 바로 낙서하듯 고치면, 다른 사람이 그 잘못된 차트를 그대로 믿고 일하게 되고,
|
||||
누가 언제 무엇을 바꿨는지도 뒤엉킨다. 그래서 우리는 항상 **원본을 복사한 작업용 사본(브랜치)** 에서
|
||||
고친 뒤, 담당자가 한 번 확인(리뷰)하고 나서야 원본에 반영한다.
|
||||
|
||||
이렇게 하면 좋은 점:
|
||||
|
||||
- 원본은 항상 **작동하는 상태**로 유지된다 (파일럿·시연 중에 갑자기 화면이 깨지지 않는다).
|
||||
- 실수해도 내 작업용 사본에서만 일어나므로 **되돌리기 쉽다**.
|
||||
- 바꾼 이유와 내용을 담당자가 **한 번 검토**하므로 사고가 미리 걸러진다.
|
||||
|
||||
> 한 줄 요약: **원본(main/master)에 직접 저장(커밋)·업로드(push)하지 않는다. 항상 사본(브랜치) → 확인(리뷰) → 반영 순서.**
|
||||
|
||||
---
|
||||
|
||||
## 2. 시작 준비 — GitHub 계정과 저장소 초대
|
||||
|
||||
우리는 **GitHub**라는 서비스에서 이 저장소를 관리한다. (GitHub = 저장소를 온라인에 두고 여럿이 함께
|
||||
작업하게 해주는 웹사이트.)
|
||||
|
||||
**여러분이 할 일 (체크리스트)**
|
||||
|
||||
- [ ] 1. [github.com](https://github.com) 에서 **무료 계정**을 만든다. (이메일·비밀번호만 있으면 된다)
|
||||
- [ ] 2. 가입에 사용한 **GitHub 아이디(username) 또는 이메일**을 단톡방에 알려준다.
|
||||
- [ ] 3. 소유자(윤찬)가 **여러분을 저장소에 초대**한다. → **초대 실행은 윤찬이 직접 한다. 여러분이 신청하는 것이 아니다.**
|
||||
- [ ] 4. 초대되면 여러분의 가입 이메일로 **초대 메일**이 오거나, GitHub 알림에 초대가 뜬다.
|
||||
- [ ] 5. 메일/알림의 **"Accept invitation(초대 수락)"** 버튼을 누른다. 끝.
|
||||
|
||||
수락하고 나면 저장소 페이지가 열리고, 이제 여러분도 이 프로젝트의 협업자(collaborator)가 된다.
|
||||
|
||||
> 초대가 안 왔거나 버튼을 못 찾겠으면 단톡방에 알려주면 된다. 흔한 일이니 걱정하지 않아도 된다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 실제 수정 흐름 — 웹 브라우저만으로 (권장)
|
||||
|
||||
여기서는 **컴퓨터에 아무것도 설치하지 않고, GitHub 웹사이트 화면만으로** 파일을 고치고 반영 요청까지
|
||||
하는 방법을 설명한다. 대부분의 문구·문서 수정은 이 방법으로 충분하다.
|
||||
|
||||
먼저 이 흐름의 전체 그림:
|
||||
|
||||
```
|
||||
파일 열기 → 편집(연필) 버튼 → 내용 수정 → "Propose changes(변경 제안)"
|
||||
→ 자동으로 새 브랜치(사본) 생성됨 → Pull Request(반영 요청) 작성
|
||||
→ 담당자 리뷰 → 문제 없으면 병합(main에 반영)
|
||||
```
|
||||
|
||||
용어 두 개만 더:
|
||||
|
||||
| 용어 | 한 줄 뜻 |
|
||||
|---|---|
|
||||
| **Pull Request (PR, 풀 리퀘스트)** | "제 사본에서 이렇게 고쳤으니 원본에 반영해 주세요"라는 **반영 요청서** |
|
||||
| **병합(merge, 머지)** | 검토를 통과한 사본의 변경을 원본에 **합치는 것** |
|
||||
|
||||
### 3-1. 단계별 (웹 UI 기준)
|
||||
|
||||
1. **파일을 찾아 연다.** 저장소 페이지에서 폴더를 눌러 들어가 원하는 파일을 클릭한다.
|
||||
(예: 문구는 대개 `apps/web/src/pages/` 폴더 안에 있다 — 4장 참조)
|
||||
2. **연필(✏️) 아이콘을 누른다.** 파일 오른쪽 위에 있다. "Edit this file(이 파일 편집)" 뜻이다.
|
||||
3. **내용을 고친다.** 화면에서 글자를 직접 수정하면 된다.
|
||||
4. 오른쪽 위 **"Commit changes...(변경 저장)"** 초록 버튼을 누른다.
|
||||
5. 작은 창이 뜨면:
|
||||
- **변경 설명**에 무엇을 왜 고쳤는지 한 줄로 적는다. (예: `학습자 홈 안내 문구 오타 수정`)
|
||||
- **반드시 아래쪽 "Create a new branch ... and start a pull request(새 브랜치를 만들어 PR 시작)"** 를 고른다.
|
||||
→ 이걸 고르면 **원본을 건드리지 않고 자동으로 사본(브랜치)이 만들어진다.** (첫 번째 "Commit directly to the master branch(원본에 바로 저장)"는 **고르지 않는다.**)
|
||||
- 브랜치 이름은 자동으로 채워지니 그대로 둬도 된다.
|
||||
- **"Propose changes"** 를 누른다.
|
||||
6. **Pull Request(반영 요청) 화면**이 나온다. 제목·설명을 확인하고(자동으로 채워져 있음)
|
||||
**"Create pull request"** 를 누른다.
|
||||
7. 끝. 이제 **담당자가 리뷰**한다. 단톡방에 "PR 올렸습니다"라고 한 마디 남겨주면 확인이 빨라진다.
|
||||
8. 담당자가 수정 요청을 남기면, 같은 PR에서 파일을 다시 편집해 커밋하면 된다.
|
||||
문제 없으면 담당자가 **병합(merge)** 하여 원본에 반영한다.
|
||||
|
||||
### 3-2. 한눈에 보는 체크리스트
|
||||
|
||||
- [ ] 원하는 파일을 연다
|
||||
- [ ] 연필(✏️) 버튼으로 편집
|
||||
- [ ] "Commit changes" → **새 브랜치 만들기 선택** (원본 직접 저장 아님)
|
||||
- [ ] "Create pull request"로 반영 요청
|
||||
- [ ] 단톡방에 PR 올렸다고 공유
|
||||
- [ ] 리뷰 통과 후 담당자가 병합 → 완료
|
||||
|
||||
---
|
||||
|
||||
## 4. UI 문구(화면 글자) 수정 프로세스
|
||||
|
||||
### 4-1. 기본 경로 — 단톡방으로 목록 전달 (가장 쉽고 빠름)
|
||||
|
||||
화면에 보이는 문구를 고치고 싶을 때 **가장 권장하는 방법**이다.
|
||||
|
||||
1. **바꿀 문구를 목록으로 정리**한다. 아래처럼 "어디의 / 무엇을 / 어떻게" 형태면 개발자가 바로 반영한다.
|
||||
|
||||
| 위치(어느 화면) | 현재 문구 | 바꿀 문구 |
|
||||
|---|---|---|
|
||||
| 학습자 홈 상단 | 회기를 시작하세요 | 오늘의 회기를 시작해 보세요 |
|
||||
| 회기 준비 화면 버튼 | 시작 | 회기 시작 |
|
||||
|
||||
2. 이 목록을 **단톡방에 전달**한다.
|
||||
3. 개발 쪽에서 **단순 텍스트 교체 수준으로 즉시 반영**한다.
|
||||
|
||||
문구 수정은 이 경로가 기본이다. Git을 몰라도 되고, 실수할 일도 없다.
|
||||
|
||||
### 4-2. 직접 고쳐보고 싶은 분을 위한 안내
|
||||
|
||||
3장의 웹 UI 방법으로 직접 PR을 올리고 싶다면, 문구는 주로 아래 위치에 있다.
|
||||
|
||||
- **폴더**: `apps/web/src/pages/`
|
||||
- **파일**: 화면별 `.tsx` 파일 (예: 학습자 홈은 `LearnerHome.tsx`, 회기 화면은 `Session.tsx`,
|
||||
교수자 화면은 `Professor.tsx`)
|
||||
- **문구 형태**: 파일 안에서 **따옴표(" ")로 감싼 한글 글자**가 화면에 보이는 문구다.
|
||||
예: `label: "전체"` 에서 `전체`, `"내담자 정보를 불러오는 중"` 같은 부분.
|
||||
|
||||
**가장 중요한 원칙 — 문자열(따옴표 안 글자)만 바꾸고, 코드 구조는 절대 건드리지 않는다.**
|
||||
|
||||
| 해도 되는 것 (안전) | 하면 안 되는 것 (위험) |
|
||||
|---|---|
|
||||
| 따옴표 **안쪽의 한글 글자**만 수정 | 따옴표 `"` 자체를 지우기 |
|
||||
| `"시작"` → `"회기 시작"` 처럼 글자만 교체 | `label:`, `{ }`, `( )`, `;`, `<` `>` 같은 기호 수정 |
|
||||
| 오타·띄어쓰기·표현 다듬기 | 줄을 통째로 지우거나 위치 옮기기 |
|
||||
| | 영어로 된 코드 부분(`value`, `desc` 등) 수정 |
|
||||
|
||||
> 헷갈리면 무리하지 말고 4-1의 목록으로 전달하면 된다. 그게 더 빠를 때가 많다.
|
||||
> 직접 PR을 올렸다면 단톡방에 알려서 리뷰를 받는다. 병합 전에 담당자가 확인하므로,
|
||||
> 잘못 고쳤어도 원본에 그대로 들어가지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 하지 말아야 할 것 (꼭 지켜주세요)
|
||||
|
||||
| 하지 말 것 | 왜 위험한가 |
|
||||
|---|---|
|
||||
| ❌ **main(master)에 직접 저장·업로드** | 원본이 검토 없이 바뀌어 시연·파일럿 중 화면이 깨질 수 있다. 항상 사본(브랜치)+PR로. |
|
||||
| ❌ **force push(강제 덮어쓰기)** | 남들이 올린 기록까지 지워버릴 수 있다. 이 단어가 나오면 절대 실행하지 말고 문의. |
|
||||
| ❌ **남의 브랜치(사본)를 덮어쓰기·삭제** | 다른 사람이 작업 중인 내용을 날린다. 내 사본만 다룬다. |
|
||||
| ❌ **코드 파일을 대량으로 수정** | 문구 한두 줄이 아니라 구조를 건드리면 프로그램이 멈춘다. 문구는 "문자열만" 원칙. |
|
||||
| ❌ **`.env`·비밀번호·API 키 등 secret 파일 커밋** | 비밀 정보가 온라인에 영구히 노출된다. 이런 파일은 올리지 않는다. |
|
||||
| ❌ **확신 없는 대량 삭제** | 무엇이 사라질지 모른다. 애매하면 먼저 물어본다. |
|
||||
|
||||
> 위 항목 중 하나라도 "이거 해도 되나?" 싶으면 **멈추고 단톡방에 물어보는 것이 정답**이다.
|
||||
> 물어봐서 손해 보는 일은 없다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 도움 요청 채널
|
||||
|
||||
- **막히거나 헷갈릴 때: 단톡방**에 바로 물어본다. 캡처(스크린샷)를 함께 올리면 더 빠르다.
|
||||
- 자주 나오는 질문
|
||||
- "초대가 안 왔어요" → 단톡방에 GitHub 아이디/이메일을 다시 알려준다.
|
||||
- "어떤 버튼을 눌러야 할지 모르겠어요" → 화면 캡처와 함께 물어본다.
|
||||
- "문구만 바꾸고 싶어요" → 4-1의 목록 양식으로 단톡방에 전달하면 개발 쪽에서 반영한다.
|
||||
|
||||
혼자 오래 고민하지 말고, 처음엔 무엇이든 물어보면서 익히면 된다.
|
||||
|
||||
---
|
||||
|
||||
### (선택 심화) 로컬 Git으로 작업하기
|
||||
|
||||
컴퓨터에 직접 도구를 설치해 작업하는 방법도 있지만, **연구팀·디자인 담당은 위 3장의 웹 UI 방법만으로
|
||||
충분하다.** 로컬 개발 환경이 필요한 경우(개발 인력 합류 등)에는 별도 문서
|
||||
[`local-development.md`](./local-development.md)를 참고한다. 이 문서를 처음부터 볼 필요는 없다.
|
||||
|
|
@ -488,8 +488,8 @@ RAG 임베딩/리랭커 의존성은 기본 이미지에 설치하지 않는다.
|
|||
```powershell
|
||||
# 백엔드 (apps/api)
|
||||
cd apps\api
|
||||
python -m pytest app/ -q # 백엔드 기준선 178 pass
|
||||
python -m pytest engine_gateway\ -q # 현재 27 pass
|
||||
python -m pytest app/ -q # 백엔드 기준선 400 pass
|
||||
python -m pytest engine_gateway\ -q # 현재 29 pass
|
||||
|
||||
# 웹 (apps/web)
|
||||
cd apps\web
|
||||
|
|
|
|||
|
|
@ -136,8 +136,8 @@ rg -n "materialize_seed_personas|init_pool|close_pool|--apply|--json" scripts/ma
|
|||
|
||||
```powershell
|
||||
# 백엔드 (작업 디렉터리 apps/api)
|
||||
python -m pytest app/ -q # 백엔드 기준선 178 pass
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 현재 27 pass
|
||||
python -m pytest app/ -q # 백엔드 기준선 400 pass
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 현재 29 pass
|
||||
|
||||
# 프론트 (작업 디렉터리 apps/web)
|
||||
npm run typecheck
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
|
|||
|
||||
| 검증 | 작업 디렉터리 | 명령 | DB | API(8000) | 웹(5173) | 엔진GW(9099) | 브라우저 | 현재 통과 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 현재 collect-only 349 tests, 최신 focused pass |
|
||||
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 현재 27 pass |
|
||||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-07-15 전체 실행 400 passed |
|
||||
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 현재 29 pass |
|
||||
| API 타입 생성 체크 | `apps/web` | `npm run check:api-types` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||
| 웹 타입체크 | `apps/web` | `npm run typecheck` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||
| 웹 빌드 | `apps/web` | `npm run build` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 194 tests / 19 files |
|
||||
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 215 tests / 20 files |
|
||||
|
||||
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
|
||||
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
|
||||
|
|
@ -42,15 +42,15 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
|
|||
|
||||
```sh
|
||||
# apps/api
|
||||
python -m pytest app/ -q # 앱 단위 테스트: 현재 collect-only 349 tests, 최신 focused pass
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트 현재 27 pass
|
||||
python -m pytest app/ -q # 앱 단위 테스트: 2026-07-15 전체 실행 400 passed
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트 현재 29 pass
|
||||
```
|
||||
|
||||
수집만 빠르게 확인하려면:
|
||||
|
||||
```sh
|
||||
python -m pytest app/ --collect-only -q # 현재: "349 tests collected"
|
||||
python -m pytest engine_gateway/ --collect-only -q # 현재: "27 tests collected"
|
||||
python -m pytest app/ --collect-only -q # 현재: "400 tests collected" (2026-07-15)
|
||||
python -m pytest engine_gateway/ --collect-only -q # 현재: "29 tests collected"
|
||||
```
|
||||
|
||||
> 참고: 실행 중 `PendingDeprecationWarning: Please use 'import python_multipart'`
|
||||
|
|
@ -221,7 +221,10 @@ python -m uvicorn app.main:app --host 127.0.0.1 --port 8000
|
|||
|
||||
```sh
|
||||
# apps/web — API(8000)와 DB가 떠 있는 상태에서
|
||||
npm run e2e # = playwright test (전체)
|
||||
npm run e2e # 전체: fixture 병렬 단계 → DB/engine 직렬 단계
|
||||
npm run e2e:list # 두 단계의 수집 목록·개수만 확인
|
||||
npm run e2e:parallel # desktop/mobile route-fixture, workers=4
|
||||
npm run e2e:single-run # DB/engine @single-run, workers=1
|
||||
npm run e2e:headed # 브라우저 표시
|
||||
npm run e2e:ui # Playwright UI 모드
|
||||
```
|
||||
|
|
@ -230,9 +233,9 @@ npm run e2e:ui # Playwright UI 모드
|
|||
|
||||
```sh
|
||||
npx playwright test e2e/session-layout.spec.ts
|
||||
npx playwright test --grep "@single-run" # 직렬 실행 시나리오만
|
||||
npx playwright test --project=chromium-single-run --workers=1 --grep "검색어"
|
||||
npx playwright test --grep-invert "@single-run" # 병렬 시나리오만
|
||||
npx playwright test --list # 실행하지 않고 목록·개수만
|
||||
npm run e2e:list # 실행하지 않고 단계별 목록·개수만
|
||||
```
|
||||
|
||||
유용한 오버라이드(`playwright.config.ts` 실측):
|
||||
|
|
@ -245,30 +248,38 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|
|||
|
||||
### 3.5 프로젝트(브라우저 프로파일) 구성
|
||||
|
||||
`playwright.config.ts`는 `testDir: ./e2e`, `timeout: 30s`, `expect.timeout: 5s`,
|
||||
`fullyParallel: true`로 다음 프로젝트를 정의한다.
|
||||
`playwright.config.ts`는 `testDir: ./e2e`, `timeout: 30s`, `expect.timeout: 5s`로
|
||||
다음 프로젝트를 정의한다. fixture 프로젝트는 `fullyParallel:true`로 실행한다. `npm run e2e`는
|
||||
로컬 단일 API/DB/engine 자원을 보호하기 위해 desktop/mobile 단계를 `workers=4`로 먼저 끝내고,
|
||||
`chromium-single-run`을 `fullyParallel:false`, `workers=1`로 두 번째 실행한다.
|
||||
|
||||
| 프로젝트 | 뷰포트/디바이스 | 대상 grep |
|
||||
|---|---|---|
|
||||
| `chromium-desktop` | 1440×900 | `@single-run`·`@public-auth` 제외 전부 |
|
||||
| `chromium-mobile` | Pixel 5 | `@single-run`·`@public-auth` 제외 전부 |
|
||||
| `chromium-single-run` | 1280×800 | `@single-run`만(직렬) |
|
||||
| `chromium-single-run` | 1280×800 | `@single-run`만(별도 2단계, workers=1 직렬) |
|
||||
| `chromium-public-auth` | 1440×900 | `E2E_PUBLIC_AUTH=1`일 때만 활성, 공개 사이트 대상 |
|
||||
|
||||
- CI(`process.env.CI`)에서는 `retries: 2`, `workers: 1`, `list`+`html` 리포터를 쓴다.
|
||||
- 로컬 fixture 단계의 기본 worker는 4다. 전체 러너에서만 `E2E_PARALLEL_WORKERS`로 조정할 수 있고,
|
||||
12-worker 전면 병렬은 단일 FastAPI/DB가 포화되어 서로 무관한 요청 timeout을 만들므로 기준선으로 쓰지 않는다.
|
||||
- `npx playwright test`를 직접 실행하면 로컬에서 세 프로젝트가 같은 worker pool에 섞일 수 있다.
|
||||
전체 게이트는 반드시 `npm run e2e`, focused DB/engine 게이트는 프로젝트와 worker를 명시한
|
||||
`npx playwright test --project=chromium-single-run --workers=1 --grep "..."`을 사용한다.
|
||||
- 실패 시 trace(첫 재시도)·스크린샷·비디오를 `node_modules/.tmp/`에 남긴다.
|
||||
|
||||
### 3.6 실측 테스트 개수 (현재)
|
||||
|
||||
`npx playwright test --list` 기준 **총 194 tests / 19 files**.
|
||||
`npx playwright test --list` 기준 **총 215 tests / 20 files**.
|
||||
|
||||
- **병렬 시나리오**: 150 tests (desktop 75 + mobile 75)
|
||||
- **`@single-run` 직렬 시나리오**: 44 tests (DB 영속화·세션 MVP·음성 성공경로·회기말 평가 저장·교수자 명시 재평가 저장·교수자 턴 재평가 저장·교수자 UI 평가 재시도·교수자 사용자별 분석·source pack sync·이론모드 저장·동의 철회 후 voice 차단·브라우저 stream PII 마스킹·음성 transcript 저장 실패 UI 표면화 등)
|
||||
- **병렬 시나리오**: 166 tests (desktop 83 + mobile 83)
|
||||
- **`@single-run` 직렬 시나리오**: 49 tests (DB 영속화·세션 MVP·음성 성공경로·인증 시각 테마·회기말 평가 저장·교수자 명시 재평가 저장·교수자 턴 재평가 저장·교수자 UI 평가 재시도·교수자 사용자별 분석·source pack sync·이론모드 저장·동의 철회 후 voice 차단·브라우저 stream PII 마스킹·음성 transcript 저장 실패 UI 표면화 등)
|
||||
- 2026-07-15 전체 검증: `npm run e2e:parallel` **166/166 passed**(2.5분), `npm run e2e:single-run` **49/49 passed**(23.2분). 합계 **215/215 passed**. AI 튜터 DB 영속화는 정상 provider 응답이 2분을 넘을 수 있어 해당 테스트만 같은 실엔진 묶음의 장시간 기준인 240초를 사용한다.
|
||||
- `e2e/voice-success.spec.ts`는 직접 `/voice/ws` 캐스케이드, Session 텍스트 턴의
|
||||
`POST /voice/speech`, Session 마이크 UI를 함께 검증한다. 브라우저 `<audio>.play()`가 차단된 조건에서도
|
||||
Web Audio buffer source 재생이 시작되는지와 Session 마이크 UI가 `audio_end`에 browser voice
|
||||
activity/silence 메타를 싣는지 확인한다.
|
||||
- 2026-07-13 focused 검증: `PLAYWRIGHT_PORT=5271 npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run --workers=1` **2 passed**. 텍스트 턴 저장→소유 회기/턴 기반 `/voice/speech`→OpenAI speech 요청→Web Audio buffer 재생 시작을 확인하고, 별도 새 회기에서 마이크 PCM/STT→AI reply→TTS chunk/`tts_end` 경로가 유지되는지 검증한다. 백엔드 voice focused는 **32 passed**이며, 운영 키 직접 smoke는 `gpt-4o-mini-tts`가 98,133-byte MP3(11.68초)를 반환했다.
|
||||
- 2026-07-13 focused 검증: `PLAYWRIGHT_PORT=5269 npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run --workers=1 --grep "drives one voice turn"` **1 passed**. 같은 로그인에서 텍스트 턴 저장→소유 회기/턴 기반 `/voice/speech`→OpenAI speech 요청→Web Audio buffer 재생 시작을 확인하고, 별도 새 회기에서 마이크 PCM/STT→AI reply→TTS chunk/`tts_end` 경로가 유지되는지 검증한다. 백엔드 voice focused는 **32 passed**이며, 운영 키 직접 smoke는 `gpt-4o-mini-tts`가 98,133-byte MP3(11.68초)를 반환했다.
|
||||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5205 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` **7 passed**. 실제 브라우저 `openSessionStream()` → `/sessions/{id}/stream` → DB-backed `/review` 축어록 저장 경로, AI 튜터 코칭 이력 저장/재로딩, WebSocket `stt_result` 음성 비언어 메타데이터, Session 마이크 UI가 생성한 voice activity/silence 메타, Phase 3 pre/post 점수의 DB-backed 저장/재조회, 그리고 세션 종료 background deep 평가가 durable DB row로 저장되어 교수자 리뷰가 `평가 완료`로 전환되는지 검증한다. AI 튜터 코칭 이력 검증은 `POST /live-coach` 응답과 DB-backed history payload의 `status=ready`, `latency_ms>0`도 확인해 규칙 기반 `degraded` fallback 200 응답이 정상 AI 코칭으로 통과하지 못하게 한다.
|
||||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5238 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "finishes session end evaluation"` **1 passed**. 같은 자동 종료 평가 row가 `/teacher/dashboard`의 `recent_sessions`에서도 `evaluation_status=ready`, `review_ready=true`, `supervisor_state=평가 완료`로 반영되는지 실제 DB/API/엔진으로 검증한다.
|
||||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5237 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "explicit teacher session reevaluation"` **1 passed**. 실제 DB/API/엔진에서 학습자 세션과 턴을 만든 뒤 교수자 `POST /eval/sessions/{id}/reevaluate`가 durable `app.session_evaluation` row를 저장하고, `/eval/.../evaluation` 및 `/review`가 `평가 완료`로 반영되는지 검증한다.
|
||||
|
|
@ -276,6 +287,7 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|
|||
- 2026-07-01 추가 DB-backed UI 검증: `PLAYWRIGHT_PORT=5245 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "manual AI evaluation retry"` **1 passed**. admin engine config를 잠깐 실패 endpoint로 바꿔 실제 `app.session_evaluation` 실패 row를 만든 뒤 원복하고, 교수자 리뷰 UI의 `AI 평가 재시도` 버튼 클릭이 실제 `POST /eval/sessions/{id}/reevaluate` → durable ready row → `/review` `평가 완료` → `/teacher/dashboard` `evaluation_status=ready`까지 이어지는지 검증한다.
|
||||
- 2026-07-02 focused 검증: `PLAYWRIGHT_PORT=5255 npx playwright test e2e/kb-source-packs.spec.ts --project=chromium-single-run --workers=1` **1 passed**. 관리자 전용 `POST /kb/live-coach/source-packs/sync`가 인증 없이 401, learner 403, admin 202로 동작하고, 0615 워크북·DSM·공식 상담 지침·자살위험 지침 source pack을 DB-backed evaluator RAG에 sync한 뒤 source-scoped `/kb/eval-grounding`이 503 skip 없이 200을 반환하고 같은 `source_id`만 반환하는지 검증한다.
|
||||
- 2026-07-02 focused 검증: `PLAYWRIGHT_PORT=5259 npx playwright test e2e/session-mvp.spec.ts --project=chromium-desktop --workers=1 --grep "AI tutor|stale empty AI tutor quota|quota exhaustion|degraded AI tutor|voice conversation stop"` **7 passed**. route-fixture UI에서 AI 튜터의 stale empty quota가 서버 `GET /live-coach` 재조회 뒤 실제 잔여 기회로 복구되는지, 최신 quota exhaustion 오류가 이전 코칭 카드에 가려지지 않는지, 음성 `reply.conversation_stopped`가 빈 client reply 대신 109 안전 게이트와 socket close를 유지하고 live-coach를 호출하지 않는지 고정한다. 이 fixture들은 UI 회귀 증거이며 DB-backed 엔진 성공 증거가 아니다.
|
||||
- 2026-07-03 backend focused 검증: `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**. live coach LLM 생성은 성공했지만 `audit.llm_call_log` 저장 확인이 실패한 경우 `status=ready` AI 코칭으로 보이지 않고 규칙 기반 `status=degraded` 코칭으로 내려가는지 검증한다. 기존 DB-backed event 저장 실패 503 회귀와 source metadata/degraded fallback 회귀도 같은 focused 묶음에서 유지한다.
|
||||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5251 npx playwright test e2e/session-mvp.spec.ts --project=chromium-desktop --workers=1 --grep "AI tutor|runtime AI tutor"` **4 passed**. mock MVP 코칭 카드에서 `근거 보기` 모달이 source title, `source_pack` kind, `2026-06-15` version, citation을 표시하는지 검증하고, `status=degraded` route fixture는 카드·근거 모달·이력 모달·턴 `C` 마커가 `대체 코칭`/`AI 응답 대체`를 노출하는지 고정한다. 추가 route fixture는 `GET /live-coach` 실패가 빈 이력으로 보이지 않고 alert로 표시되는지, `persistence_source/source=runtime`이 임시 저장 경고로 표시되는지도 고정한다. 이 fixture들은 UI 회귀 증거이며 DB-backed 엔진 성공 증거가 아니다.
|
||||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5252 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 --grep "pre/post"` **1 passed**. 기존 저장 pre/post 점수를 비운 입력이 `저장된 값 기준`으로 오인되지 않고 invalid 상태, alert, 저장 버튼 disabled로 표면화되는지 검증한다.
|
||||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5248 npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1 --grep "pending voice transcript"` **1 passed**. mock WebSocket이 `transcript final` 뒤 `turn_persistence_unavailable` error를 보내면 Session UI가 임시 학습자 발화를 정상 턴으로 확정하지 않고 `저장 실패` 배지와 alert로 표면화하며, 대기 중 내담자 말풍선을 제거하고 텍스트 입력을 복구하는지 검증한다.
|
||||
|
|
@ -299,26 +311,31 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|
|||
- 2026-07-01 focused 검증: `PLAYWRIGHT_BASE_URL=http://localhost:5210 npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --grep "shows selected learner analysis"` **1 passed** + `PLAYWRIGHT_BASE_URL=http://localhost:5210 npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --grep "professor (student analysis overview|learner detail analysis)"` **2 passed**. 실제 5210 로컬 스택에서 학생 분석 목록의 사용자 표시명, 상세 기본 페르소나별 회기 탭, 페르소나 행 펼침, 전체 회기 탭 전환, 7개 폭 레이아웃 containment를 검증한다.
|
||||
- 2026-07-01 focused 검증: `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider app/test_teacher_dashboard.py -q` **10 passed**. 교수자 대시보드 API는 `session_persistence.list_all_sessions()`로 전체 담당 세션을 읽고, 학습자 일반 목록은 `list_recent_sessions()`로 최근 100개 제한을 명시해 한 학생의 최신 회기가 다른 학생 분석 목록을 밀어내지 않는지 검증한다. 또한 ended session summary가 `app.session_evaluation`의 `evaluation_status`, `review_ready`, `supervisor_state`, `evaluation_error`를 별도로 싣고, 평가 실패가 수동 review status와 섞이지 않는지 검증한다.
|
||||
- 2026-07-01 focused 검증: `npx playwright test e2e/admin.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --grep "approved admin without onboarding"` **2 passed**. 승인된 `role=admin` 사용자의 세션에 `admin_access=false`, `onboarding_completed_at=null`이 남아도 `/admin`이 `/onboarding`으로 우회하지 않고 관리 콘솔의 `/admin/*` API를 호출하는지 fixture로 고정한다.
|
||||
- 2026-07-15 보태니컬 글래스 UI·SSOT 검증: `npm run check:design-ssot` + `npm run typecheck` + `npm run build` 통과, `npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --reporter=line` 시각 게이트 **15/15**, `npx playwright test e2e/auth-visual.spec.ts --project=chromium-single-run --reporter=line` **1/1**, `npx playwright test e2e/session-layout.spec.ts e2e/learner.spec.ts e2e/session-review.spec.ts --project=chromium-desktop --project=chromium-mobile` **46/46**, 로그인→온보딩 focused desktop/mobile **2/2**. 공통 AppShell GNB, Theme store, Surface variant의 소유권과 전 폭 대시보드/리뷰 탭, 로그인·온보딩 라이트/다크 390/1280px, 고해상도 보태니컬 자산을 함께 고정한다. 라이트 테마도 패널당 복수 굴절 그라데이션, backdrop blur, 헤어라인, 그림자를 computed style로 단언하며 모바일 셸이 보태니컬 배경을 제거하지 않는지 검사한다. 관리자 사용자 표는 semantic table, 정렬 헤더, 1440px 최소 폭과 표 전용 가로 스크롤을 desktop/mobile에서 검증한다.
|
||||
|
||||
레이아웃·시각 회귀 게이트(핵심 합격선):
|
||||
|
||||
| 게이트 | 스펙 | 구성 | 개수 |
|
||||
|---|---|---|---|
|
||||
| 세션 레이아웃 | `e2e/session-layout.spec.ts` | 4 테스트 × (desktop+mobile) | **8 / 8** |
|
||||
| 시각 레이아웃 게이트 | `e2e/layout-visual-gate.spec.ts` | `@single-run`, 12개 화면 × 7개 폭 검사 + 다크 테마 assertion | **12 / 12** |
|
||||
| 시각 레이아웃 게이트 | `e2e/layout-visual-gate.spec.ts` | `@single-run`, 14개 화면 × 7개 폭 검사 + 다크 테마 assertion + 학습 대시보드 라이트 자산 연결 | **14 / 14** |
|
||||
| 인증 테마 게이트 | `e2e/auth-visual.spec.ts` | 로그인·온보딩 × light/dark × 390/1280px, 100vw/100dvh 및 overflow 검사 | **1 / 1** |
|
||||
| 레이아웃 포커스(재설계 화면) | `session-layout`·`session-review`·`admin`·`learner`·`settings`·`teacher`, `@single-run` 제외 | desktop+mobile 병렬 | **54** |
|
||||
|
||||
> `layout-visual-gate`는 7개 폭(390/720/861/900/1024/1280/1440)에서 12개 핵심 화면의 가로
|
||||
> `layout-visual-gate`는 7개 폭(390/720/861/900/1024/1280/1440)에서 14개 핵심 화면(페르소나 운영·작성 단계 포함)의 가로
|
||||
> 오버플로·잘린 컨트롤·다크 테마 적용을 검사하고 전체 페이지 스크린샷을
|
||||
> `node_modules/.tmp/layout-gate/`에 남긴다.
|
||||
> `node_modules/.tmp/layout-gate/`에 남긴다. 학습 대시보드는 추가로 라이트 테마의 카드·사이드바·
|
||||
> 우상단 배경 자산이 실제 computed style에 연결됐는지 확인하고 1200px·390px 라이트 캡처를 남긴 뒤 다크로 복귀한다.
|
||||
> `session-layout`은 회기 전/활성 화면이 뷰포트를 벗어나지 않는지, 우측 패널이 코어 영역을
|
||||
> 침범하지 않는지, 시작 후 실제 `session_id` URL에서 새로고침해도 활성 회기 상세가 유지되는지,
|
||||
> 스트림 실패 시 미저장 전사가 남지 않는지를 검증한다.
|
||||
|
||||
### 3.7 공개 인증 스모크(선택)
|
||||
|
||||
`e2e/public-auth-turn.spec.ts`는 공개 사이트(`https://vignette.chanpaca.net`)를 직접 타격하는
|
||||
옵트인 스모크로, 로컬 웹 서버를 띄우지 않는다. 절차는 `apps/web/e2e/README.md` 참고
|
||||
`e2e/public-auth-turn.spec.ts`와 `e2e/public-admin-visual.spec.ts`는 공개 사이트
|
||||
(`https://vignette.chanpaca.net`)를 직접 타격하는 옵트인 스모크로, 로컬 웹 서버를 띄우지 않는다.
|
||||
관리자 시각 스모크는 운영 홈·사용자·권한·티켓의 본문 노출과 브라우저 `pageshow` 탭 복원 뒤
|
||||
스크롤/heading 가시성을 함께 검증한다. 절차는 `apps/web/e2e/README.md` 참고
|
||||
(`E2E_PUBLIC_AUTH=1`, `npx playwright codegen ... --save-storage`로 인증 상태 캡처 후
|
||||
`E2E_PUBLIC_STORAGE_STATE` 재사용). 캡처한 storage state에는 API 세션 쿠키가 들어 있으니
|
||||
민감 정보로 취급한다.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue