vignette/apps/web
Yun Chan 93dd8f82d7 G8 실제 rollback 증명 종료와 비-secure origin 회기 리뷰 크래시 수정
G8 마지막 게이트인 receipt-bound 실제 image rollback을 격리 NAS
vignette-preview-20260807 에서 실행해 종료했다.

Gate6 계약 정정:
감사 대상 current API 이미지가 com.docker.compose.project/service/version
image label 을 갖고 있어 "helper 의 compose label 0개" 계약은 감사되지 않은
다른 이미지를 쓰지 않는 한 성립하지 않는다. 계약을 key 부재가 아니라
소속(membership) 으로 바꿔 launch-nas-preview-g8-helpers.py 에 구현했다.
image 상속 label 을 baseline 으로 읽고 container 의 모든 compose label 이
baseline 과 같거나 선언된 격리 override 인지 검사하며, 최종 project 는
target 이 아니고 service 는 api/web/db/proxy 가 아니어야 한다. docker run
argv 에 target label 을 주입하면 fake-runner 테스트가 먼저 깨진다 (37/37).

실행 결과:
- rollback-old  receipt nas-g8-723eeef22eab05e63e3fafb0 -> 79ec../c530..
- restore-current receipt nas-g8-2738846cf2cf4fbe8ce0fc26 -> 52e0../6fdb..
- release gate/approval 각 2회 멱등, audit.ci_lifecycle_event rollback/executed 2,
  audit.ci_human_approval_event authorize_rollback 2, silent auto-promotion 0
- HMAC journal 6-record 체인 검증, health 3/3, OpenAPI 126, auth 401, Web 200
- helper 0, listener 0, 비밀 env 파기. down/volume rm/prune 미실행, 공개 런타임 미접촉
- 계획했던 Windows SSH 터널은 NAS sshd 가 direct-tcpip 를 거부해 사용할 수 없어
  sshd 설정 변경 대신 같은 격리 계약의 NAS-side probe 컨테이너로 실행했다

비-secure origin 크래시 수정:
배포된 NAS 프리뷰(평문 HTTP, 비-localhost)에 회기 스펙을 돌려 24건 실패를 확인했고
원인은 하나였다. crypto.randomUUID 는 secure context 전용인데 제품 코드 18곳이
fallback 없이 호출했고 RuptureRepairCard 는 렌더 시점 호출이라 회기 리뷰 라우트
전체가 error boundary 로 떨어졌다. 릴리스 게이트 108/108 은 localhost 후보 스택에서만
돌아 이 경로를 밟은 적이 없다. src/lib/uuid.ts 의 randomUuid() 로 통일하고 fallback 도
crypto.getRandomValues 를 우선 사용해 idempotency key 의 예측 불가능성을 유지했다.
회귀는 insecure-context-uuid.spec.ts 6/6 으로 고정했다(직접 호출 0건 검사 포함).
이 수정은 아직 NAS 에 배포하지 않았다.

검증:
API 898, gateway 58, executor 28, probe 11, helper launcher 37, release agent 21,
ruff clean, web api-types/typecheck/build, SSOT FAIL 0, SSOT unit 5/5,
dashboard E2E 10/10, 학생 폐루프 실 DB 브라우저 4/4(일회용 클론),
crypto 수정 후 기존 스펙 회귀 70/70, 복원된 NAS 실제 브라우저 SSE->DB 리뷰 PASS.

부수 발견(열린 항목):
공개 API 가 engine=false 로 degraded 인데 워치독이 이를 감지하지 못한다. engine 판정이
게이트웨이 /health 의 ok 만 보고 claude readiness probe 를 돌리지 않기 때문이다.
같은 .env 와 같은 CLI 로 새 게이트웨이를 다른 포트에 띄우면 즉시 ready 이므로 상주
프로세스의 세션만 죽은 형태다. TODO A절과 대시보드에 기록했다.

이 커밋은 파일 단위로 담겼다. 위 파일들에는 이전 세션의 미커밋 G0~G8 작업이 함께
들어 있으며, hunk 를 쪼개면 대시보드/체커/TODO 정합성이 깨져 SSOT 체커가 실패한다.
2026-08-07 22:17:20 +09:00
..
e2e G8 실제 rollback 증명 종료와 비-secure origin 회기 리뷰 크래시 수정 2026-08-07 22:17:20 +09:00
functions/live2d 대시보드 폴드아웃/드릴다운 정리 + 페르소나 역린·misconduct 반응 + 게이트웨이 격리·RAG 비차단 수정 2026-06-27 02:30:46 +09:00
public 세션 화면 디자인 개편 2026-07-31 09:19:42 +09:00
scripts feat: 운영 안정성과 세션 음성 경험 개선 2026-07-31 00:13:08 +09:00
src G8 실제 rollback 증명 종료와 비-secure origin 회기 리뷰 크래시 수정 2026-08-07 22:17:20 +09:00
.dockerignore 음성 재생과 운영 배포 정리 2026-06-28 12:18:20 +09:00
.gitignore feat: P1 풀빌드 — React 프론트 7화면 + 백엔드 상담루프·평가·음성·RAG 2026-06-25 23:37:22 +09:00
Dockerfile 음성 재생과 운영 배포 정리 2026-06-28 12:18:20 +09:00
index.html feat: 운영 안정성과 세션 음성 경험 개선 2026-07-31 00:13:08 +09:00
knip.json 전 저장소 리팩터링과 SSOT 정비 2026-07-15 21:31:30 +09:00
nginx.conf feat(infra): 이식 가능한 Docker 배포 구성 2026-06-25 21:24:19 +09:00
package-lock.json 전수 E2E 순회·소유자 결정 6건 구현·디자인 감사 반영 2026-07-27 14:25:24 +09:00
package.json feat: 운영 안정성과 세션 음성 경험 개선 2026-07-31 00:13:08 +09:00
playwright.config.ts 전 저장소 리팩터링과 SSOT 정비 2026-07-15 21:31:30 +09:00
README.md 현재 작업 전체 반영 2026-06-27 16:08:41 +09:00
tsconfig.app.json feat: P1 풀빌드 — React 프론트 7화면 + 백엔드 상담루프·평가·음성·RAG 2026-06-25 23:37:22 +09:00
tsconfig.json feat: P1 풀빌드 — React 프론트 7화면 + 백엔드 상담루프·평가·음성·RAG 2026-06-25 23:37:22 +09:00
tsconfig.node.json feat: P1 풀빌드 — React 프론트 7화면 + 백엔드 상담루프·평가·음성·RAG 2026-06-25 23:37:22 +09:00
vite.config.ts 배포 도메인 관리자 페이지 블랭크 수정 (vite preview allowedHosts) 2026-06-30 12:16:51 +09:00

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/타입이 안정 계약이다.
  • ClientAvatar props(persona/state/affect/analyser)는 확정 인터페이스. avatar 에이전트는 이 파일 내부 SVG/모션만 고도화하고 시그니처는 유지.
  • 세션 데이터는 lib/api.tssessionApi(start/turn/end/stream) 사용. SSE 토큰 수신은 openSessionStream(sessionId, { onToken, onDone, ... }).
  • 페이지는 default export. AppShell로 감싸면 톱바/네비/역할 accent가 자동 적용.