pre 게이트 카드가 행 높이만큼 늘어나면서 base flex의 align-items:center가 살아남아 3축 블록이 1fr 행 한가운데 떠 있었다. 1920x1080에서 카드의 66.5%가 빈 공간이고 축 위아래로 각 277px의 죽은 여백이 생겼다. 척도 라벨은 0.54rem에 nowrap+ellipsis라 901~1279px에서 최대 12개가 잘렸고, 620px 이하에서는 15개 전부 display:none으로 사라져 숫자만 남았다. 320x568에서는 접힌 mid 전용 40px 고정 행이 게이트에도 걸려 카드가 눌리고 헤더와 축이 겹쳤다. - 게이트 카드는 align-self:center + max-height:100% + 내부 스크롤로 중앙에 서고 높이는 내용이 정한다 - 3열 격자를 축별 세로 스택(질문 한 줄 + 전폭 5칸 척도)으로 바꿔 어느 폭에서도 라벨을 자르거나 숨기지 않는다. word-break:keep-all + overflow-wrap:normal로 어절 경계에서만 줄바꿈한다 - 정보가 없던 3 AXES 배지를 제출 버튼이 왜 잠겨 있는지 답하는 0/3 진행 표시로 교체한다 - 40px 고정 행 규칙을 :not(.sx-alliance-gated)로 접힌 mid 전용으로 되돌린다 - 선택 상태 글자색을 반투명 --surface에서 --text-on-accent로 고친다 시간 만료 상태에서 드러난 네 결함도 함께 닫는다. sx-timebar와 종료 모달 패널은 다크 stage 팔레트 시절 잔재로, 정의된 적 없는 --bg-panel의 하드코딩 다크 폴백 위에 테마 토큰 글자를 얹어 라이트에서 대비가 1.13:1까지 떨어졌다. 의미색 토큰과 불투명 표면으로 되돌렸다. sx-page--active > * 의 position:relative가 같은 특이도로 뒤에 있어 종료 모달의 fixed를 덮어써 오버레이가 1532x32px 띠로 눌렸고, 게이트 행 정의는 :has() 두 개짜리 규칙보다 특이도가 낮아 시간 만료 바가 뜨면 밀려나 빈 트랙이 남았다. 둘 다 선택자를 맞춰 정정했다. 계측: 빈 공간 0.665→0.127, 라벨 잘림 12→0, 숨김 15→0, 최소 탭 46x60, 가로 오버플로 0. 배너 대비 1.13→13.88, 모달 제목 2.68→15.79(다크 13.86/15.18). 검증: typecheck, session-layout+alliance-pulse+layout-visual-gate 31/31, 시간만료 종료 다이얼로그 2/2, pre/mid 잠금 2/2, full-sweep+self-directed 30 pass. 남은 음성 4건은 baseline에서도 동일 실패하는 기존 회귀다. |
||
|---|---|---|
| .. | ||
| e2e | ||
| functions/live2d | ||
| public | ||
| scripts | ||
| src | ||
| .dockerignore | ||
| .gitignore | ||
| Dockerfile | ||
| index.html | ||
| knip.json | ||
| nginx.conf | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.ts | ||
| README.md | ||
| tsconfig.app.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
apps/web — Vignette 프론트엔드
React 19 + Vite + TypeScript + react-router v6. 상태는 Context(AuthContext) 중심.
3역할(학습자/교수자/관리자) 공통 앱 셸 + 상담 세션 UI + 가상 내담자 아바타.
디자인 단일진실원본: docs/DESIGN_CONCEPT.md + docs/dev_dashboard.html(토큰).
실행
cd apps/web
npm install
npm run dev # http://localhost:5173 (개발 서버, /api → :8000 프록시)
기타 스크립트:
npm run build # tsc -b + vite build → dist/
npm run generate:api-types # FastAPI OpenAPI → src/lib/api.gen.ts
npm run check:api-types # 생성 타입 stale 체크
npm run generate:live2d-assets
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가 백엔드로 라우팅 |
세션 아바타는 first-party SVG Live2D parameter rig를 사용한다. P4~P7 모델은
components/avatar/live2dModel.ts의 모델 계약으로 관리하며, 각 모델은 28개 표정 모션을
갖는다. npm run generate:live2d-assets는 이 계약에서
public/live2d/personas/{p4..p7}의 model3.json/exp3.json 산출물을 재생성한다.
Mao/Haru 같은 샘플 Live2D 자산과 Pixi/Cubism 런타임은 공개 배포물에서 제거했다.
라우트
| 경로 | 페이지 | 역할 | 비고 |
|---|---|---|---|
/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.
api.gen.ts FastAPI OpenAPI에서 생성한 백엔드 DTO 타입.
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 가상 내담자 SVG Live2D parameter rig.
avatar/live2dModel.ts P4~P7 모델/표정 모션 계약.
public/live2d/personas/ 생성된 first-party Live2D-compatible model3/exp3 자산.
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/타입이 안정 계약이다. ClientAvatarprops(persona/state/affect/analyser)는 확정 인터페이스. avatar 에이전트는 이 파일 내부 SVG/모션만 고도화하고 시그니처는 유지.- 세션 데이터는
lib/api.ts의sessionApi(start/turn/end/stream) 사용. SSE 토큰 수신은openSessionStream(sessionId, { onToken, onDone, ... }). - 페이지는
default export.AppShell로 감싸면 톱바/네비/역할 accent가 자동 적용.