# apps/web — Vignette 프론트엔드 React 19 + Vite + TypeScript + react-router v6. 상태는 Context(AuthContext) 중심. 3역할(학습자/교수자/관리자) 공통 앱 셸 + 상담 세션 UI + 가상 내담자 아바타. 디자인 단일진실원본: `docs/DESIGN_CONCEPT.md` + `docs/dev_dashboard.html`(토큰). ## 실행 ```bash cd apps/web npm install npm run dev # http://localhost:5173 (개발 서버, /api → :8000 프록시) ``` 기타 스크립트: ```bash npm run build # tsc -b + vite build → dist/ npm run preview # 빌드 결과 미리보기 npm run typecheck # tsc --noEmit (타입 체크만) ``` 백엔드(FastAPI)는 `apps/api`에서 `uvicorn app.main:app --port 8000`으로 띄운다. 프론트는 실제 API 세션과 DB 기반 데이터를 사용한다. 로컬 개발 로그인은 `AUTH_DEV_LOGIN_ENABLED=true`인 개발 API에서만 제공되며, 공개 환경은 Google OAuth만 사용한다. ### 환경변수 | 변수 | 기본값 | 설명 | |---|---|---| | `VITE_API_BASE` | `/api` | API 베이스. 개발은 vite proxy, 프로덕션은 nginx가 백엔드로 라우팅 | | `VITE_LIVE2D_CUBISM_CORE` | `/live2d/live2dcubismcore.min.js` | Cubism Core JS URL. 페르소나별 `live2dModelUrl`이 있을 때만 사용 | Live2D 샘플 자산은 `public/live2d/` 아래에 로컬 통합 테스트용으로 남겨 둔다. 세션 아바타는 기본적으로 SVG/persona 렌더러를 사용하며, 실제 페르소나 전용 `AvatarPersona.live2dModelUrl`이 있을 때만 Cubism 모델을 렌더링한다. Mao/Haru 샘플 모델을 모든 페르소나의 공용 프로덕션 폴백으로 쓰지 않는다. 프로덕션 빌드는 번들된 Mao/Haru 샘플 URL을 무시하고 SVG 아바타를 유지한다. 모델이 없거나 로딩에 실패하면 세션 화면은 기존 SVG 아바타를 그대로 렌더링한다. ## 라우트 | 경로 | 페이지 | 역할 | 비고 | |---|---|---|---| | `/login` | Login | — | 실제 Google OAuth + 개발 API 한정 로컬 로그인 | | `/learn` | LearnerHome | learner | 실제 persona catalog와 서버 세션 기록 | | `/learn/session/:sessionId` | Session | learner | 실제 세션 시작/이어하기 + 서버 SSE 응답 | | `/learn/session/:sessionId/review` | SessionReview | learner | 저장된 세션 기반 리뷰/기록 | | `/teach` | Professor | teacher | 실제 담당 학습자/회기 요약 | | `/admin` | Admin | admin | 실제 사용자 관리, health, AI 운영 설정 | | `/settings` | Settings | 전체 | 실제 계정/환경설정, 관리자 AI 운영 설정 | | `/` | → 역할 홈 | — | 미인증이면 `/login` | 보호 라우트는 `AuthContext` 기반 `RequireAuth` 가드(미인증 → `/login`, 역할 불일치 → 자신의 역할 홈). 과설계 없음. ## 구조 ``` src/ main.tsx 엔트리. 스타일 import 1회. App.tsx 라우터 + 역할 가드. vite-env.d.ts import.meta.env 타입. styles/ tokens.css ★ 디자인 토큰 (라이트/다크/역할 accent). dev_dashboard 계승. global.css reset + body + 스크롤바 + 유틸. lib/ api.ts fetch wrapper(credentials:include) + SSE 헬퍼 + 세션 API + 백엔드 타입. auth.tsx AuthContext(user/role/login/logout). body[data-role] 반영. format.ts 시간/타이머/숫자 포맷(tabular). components/ ui/ 프리미티브: Button Card Panel Kicker Badge StatLine ProgressBar Field/Input Dot Icon(inline svg) SectionHead. 배럴: index.ts → import { Button, ... } from "../components/ui" shell/ AppShell Sidebar Topbar (§6.3 공통 셸). avatar/ClientAvatar.tsx 가상 내담자 아바타(Live2D + SVG 폴백). avatar/Live2DAvatar.tsx Cubism 4/Pixi 런타임 브리지. pages/ Login + learner/teacher/admin/settings/session 화면. ``` ## 디자인 철칙 (위반 시 재작업) - border-left 강조선·좌측 사이드바 강조줄 금지 - 이모지 금지 — 아이콘은 전부 inline SVG(`components/ui/Icon.tsx`, stroke=currentColor) - 카드 격자 덤프 금지 (한 화면 한 메시지, 위계는 weight+여백) - 순흑(#000)/순백(#fff) 금지 — `--paper`/`--ink` 토큰만 - 모서리 절제: 카드/버튼 8px, 입력 6px. 원형은 dot/아바타/음성오브만 - 강조 = weight(400→600) + accent 텍스트 + accent-tint 배경 + 상단 kicker(+dot) - 빨강(`--crit-*`)은 진짜 위급/리스크에만 ## Features 단계 인계 노트 - UI 프리미티브는 `components/ui` 배럴에서 가져온다. props/타입이 안정 계약이다. - `ClientAvatar` props(`persona`/`state`/`affect`/`analyser`)는 확정 인터페이스. avatar 에이전트는 이 파일 내부 SVG/모션만 고도화하고 시그니처는 유지. - 세션 데이터는 `lib/api.ts`의 `sessionApi`(start/turn/end/stream) 사용. SSE 토큰 수신은 `openSessionStream(sessionId, { onToken, onDone, ... })`. - 페이지는 `default export`. `AppShell`로 감싸면 톱바/네비/역할 accent가 자동 적용.