20 KiB
20 KiB
Vignette Handoff
Updated: 2026-06-26 15:18 KST. 새 세션은 이 문서와
docs/DESIGN_CONCEPT.md를 먼저 읽고 이어가면 된다.
현재 상태
- Workspace:
D:\workspace\vignette - 로컬 웹 기본 포트:
http://localhost:5173 - 로컬 API 기본 포트:
http://127.0.0.1:8000 - 로컬 engine gateway 기본 포트:
http://127.0.0.1:9099 - 공개 웹:
https://vignette.chanpaca.net - 공개 API:
https://api-vignette.chanpaca.net - 앱 배포 소스 commit:
543d48c(Block legacy Live2D asset routes) - 최신 Cloudflare Pages production deploy:
0fe8f3cc, source543d48c, branchmain - Google OAuth 허용 이메일 도메인:
hs.ac.kr,twentyoz.kr frontenddesign스킬은 현재 세션의 사용 가능 스킬 목록에 없었다. 대신docs/DESIGN_CONCEPT.md를 SSOT로 사용했다.- 공개 API 터널은 현재
C:\Users\encep\.cloudflared\vignette-config.yml에서http://127.0.0.1:8001을 본다. - 공개용 API 프로세스는
127.0.0.1:8001에서ENVIRONMENT=prod로 떠 있다. 로컬 개발 API127.0.0.1:8000은 현재 내려 두었다. GET /personas는 이제 인증 필요다. 검증 당시 로컬http://127.0.0.1:8000/personas와 현재 공개https://api-vignette.chanpaca.net/personas모두 비로그인 401 확인 완료.- 로컬 웹이
http://127.0.0.1:5175처럼 다른 Vite 포트로 떠도 로그인은로컬 테스트 계정으로 계속버튼을 사용한다. Google OAuth 버튼은 로컬에서는 disabled로 둔다. 현재 OAuth callback이 공개 API로 돌아가기 때문에 로컬 Google OAuth는 로컬 세션에 붙을 수 없다. - 중복 Vite/API/gateway/cloudflared 프로세스는 정리했다. 현재 확인용 프로세스는
127.0.0.1:9099engine gateway,127.0.0.1:8001prod public API,cloudflaredtunnel 1개뿐이다.127.0.0.1:8000dev API와 임시 Vite preview는 내려 두었다. Playwright 전체 테스트는 자체 Vite web server를127.0.0.1:5173에 띄워 검증했다. - 공개 런타임 재기동 스크립트:
- start:
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1 - stop:
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\stop-public-runtime.ps1
- start:
이번 세션 완료
- 남아 있던 E2E seed persona 결합을 제거했다.
session-persistence,teacher,voice스펙은/api/personas에서 실제 DB persona를 가져와 사용한다.voice-success만 DB-offline fixture라ALLOW_SEED_PERSONA_FALLBACK=true와P1을 의도적으로 유지하고 주석을 남겼다.
- 운영 설정 fail-closed를 강화했다.
ENVIRONMENT != dev에서 dev login, seed fallback, 기본 session secret, OAuth 누락, localhost frontend/CORS를 설정 검증에서 거부한다.- API lifespan은 DB 초기화 실패 시 dev에서만 degraded fallback을 허용하고, staging/prod에서는 시작 실패로 둔다.
SettingsConfigDict(populate_by_name=True)를 추가해 테스트/코드에서 field name 기반 설정 생성이 가능하게 했다.
infra/docker-compose.yml을 깨끗한 ASCII compose로 재작성했다.ENVIRONMENT, Google OAuth client id/secret,OAUTH_REDIRECT_URI,CORS_ORIGINS,OPENAI_BASE_URL을 API 컨테이너에 명시 주입한다.- 기존 손상된 줄 때문에
OPENAI_API_KEY가 주석/중복으로 처리되던 문제를 제거했다. - 기본
FRONTEND_BASE_URL은https://vignette.chanpaca.net로 수정했다.
infra/.env.example에 운영 필수 값을 보강했다.ENVIRONMENT=prod,OAUTH_GOOGLE_CLIENT_ID,OAUTH_GOOGLE_CLIENT_SECRET- 기본 CORS에서 localhost 제거. 로컬 개발은 주석대로
ENVIRONMENT=dev와 localhost CORS를 별도 추가.
- admin UI 안정화.
- health card의 긴 engine readiness JSON이 모바일 폭을 밀어내지 않도록 줄바꿈 처리.
- backend admin health는 동적인 raw JSON detail 대신 안정적인
Engine readiness failed문구를 내려준다. - 전역 engine config를 바꾸는
db-persistenceE2E는@single-run으로 분리했다. - 사용자 생성 후 UI가 서버 목록 reload를 끝내기 전에 카드를 찾던 E2E 타이밍을 안정화했다. 생성한 이메일로 검색한 뒤 실제 서버 응답 카드만 검증한다.
- 사용자 관리 UI는 DB 사용자 저장소가 durable하지 않으면 생성/수정/비활성화를 막는다.
- AI 운영 라벨은
Claude CLI 게이트웨이,Anthropic API,OpenAI 호환,Solar처럼 실제 어댑터명으로 표시한다. - 설정의 AI 운영 패널은
/admin/health기반 응답 생성 상태를 함께 보여주되, health 지연이 설정 폼 로딩을 막지 않도록 분리했다. - 비개발 환경에서
admin_engine_configsingleton row가 없으면 runtime default로 내려가지 않고 fail-closed 한다. - 비개발 DB 장애 상태는
임시 기록이 아니라 저장소 중단으로 표시한다.
- public Google OAuth smoke 문서를 보강했다.
E2E_PUBLIC_AUTH=1에서는 로컬 Vite 서버를 띄우지 않고 공개 웹을 대상으로 한다.- storageState는 인증 쿠키를 포함하므로
node_modules/.tmp아래 민감 파일로 취급한다. - API session TTL은 현재 8시간이다.
- 공개 API prod 전환을 완료했다.
cloudflaredingress를127.0.0.1:8000에서127.0.0.1:8001로 변경했다.https://api-vignette.chanpaca.net/health가environment:"prod",db:true,engine:true를 반환한다.- public production-safe Playwright 게이트가 통과한다.
- 공개 런타임 start/stop 스크립트를 추가했다.
scripts/start-public-runtime.ps1는 prod-safe env override로 API 8001을 띄우고 health를 검증한다.- 기본 실행 시
cloudflared도 vignette config 기준으로 재시작한다. scripts/stop-public-runtime.ps1는 8001 API를 정리한다.-StopCloudflared를 붙이면 터널도 같이 정리한다.
- 공개 API 보안 경계를 보강했다.
apps/api/app/routes/personas.py에CurrentPrincipal을 붙여 persona catalog를 authenticated-only로 전환했다.- 로컬 E2E
auth.spec.ts에 비로그인/api/personas => 401회귀를 추가했다. - public readiness gate는 빈 API request context로 비로그인
/personas => 401을 확인한다. storageState가 붙은 request fixture를 쓰지 않도록 처리했다.
- 공개 웹 API base fallback을 보강했다.
apps/web/src/lib/api.ts는VITE_API_BASE가 없고 host가vignette.chanpaca.net또는*.pages.dev이면https://api-vignette.chanpaca.net을 기본 API origin으로 사용한다.- 이 변경으로 public web의
/api/auth/config가 SPA HTML로 떨어져 Google 버튼이 disabled 되는 문제를 피한다.
- 로컬 로그인 UX를 보강했다.
apps/web/src/pages/Login.tsx는 로컬 origin에서 OAuth redirect URI가 공개 API이면 Google 버튼을 disabled 처리하고로컬 테스트 계정으로 로그인안내를 보여준다.auth.spec.ts에 로컬 테스트 계정 로그인 후/learnredirect 회귀를 추가했다.
- 학습자 홈 기록 UX를 보강했다.
apps/web/src/pages/LearnerHome.tsx는 세션이 0개인 사용자에게도 실제 서버 세션 0개를 기반으로기존 회기빈 상태와 0 카운트를 보여준다.- 기록이 있을 때만
has-session-records압축 레이아웃을 쓰도록 분리했다. 0회 사용자 모바일에서도 선택 내담자 정보, 새 회기 CTA, 기록 빈 상태가 문서 스크롤 없이 보인다. apps/web/e2e/readiness.spec.ts는 fake 12회/최근 8회 부재뿐 아니라 서버 기반 0회 빈 상태를 기대하도록 갱신했다.- history의 반복 CTA는
다시 시도대신다시 연습으로 바꿔 기록 회고와 새 회기 시작 의미를 분리했다.
- 직접 세션 URL의 persona 우회 경로를 막았다.
LearnerHome에서만 막던degraded또는 non-DB persona 차단을Session직접 진입 경로에도 적용했다./learn/session/:personaCode로 직접 들어와도 DB 원본 persona가 아니면회기 시작버튼이 비활성화된다.- desktop/mobile E2E에 degraded
seed_fallbackpersona route mock 회귀를 추가했다.
- active session dense viewport를 보정했다.
apps/web/src/pages/session/session.css에서 380px 이하/낮은 화면의 stage padding과 avatar 크기를 줄여 stage 내부 clipping을 없앴다.session-layout.spec.tsdense viewport desktop/mobile 회귀와 전체 Playwright에서 확인했다.
- active session 텍스트 턴 저장/표시 흐름을 실제 SSE 계약에 맞췄다.
- 프론트
sessionApi.stream은 실제 백엔드 계약인POST /sessions/{id}/stream을 fetch stream으로 소비한다. /stream요청이 거절되거나 SSE가 열린 뒤 error 이벤트로 끝나면 학습자 발화와 부분 내담자 응답을 transcript에 남기지 않고 입력값을 복원한다.- 내담자 자막은 로컬 typewriter가 아니라 서버 token 이벤트로만 쌓는다.
- 백엔드
/turn과/stream은 엔진 성공 전 learner turn/state를 append하지 않도록 순서를 바꿨고,app.test_session_turn_persistence회귀로 검증했다.
- 프론트
- 음성 회기 실패 턴 저장/표시도 같은 기준으로 맞췄다.
- voice route는 엔진 응답 생성 성공 후에만 learner/client turn을 저장한다.
- 프론트는 음성 transcript를 pending으로 표시하고, 엔진 실패 시 pending transcript를 제거한다.
- 음성 degraded reason의 내부
seed_fallback/runtime store 표현은 사용자용 한국어 상태 문구로 바꿨다.
- admin health의 dev DB fallback 문구를
임시 기록이 아니라비영구 런타임 기록으로 정리했다. - Live2D demo fallback과 샘플 자산을 제거했다.
- Mao/Haru 샘플 모델과 Pixi/Cubism 런타임은 공개 배포물에서 제거했다.
- 세션 화면은 SVG persona avatar만 렌더링한다.
- Cloudflare edge에 남은 기존
/live2d/*캐시를 막기 위해 Pages Functionapps/web/functions/live2d/[[path]].js가 404를 반환한다.
검증 결과
- Web typecheck:
cd apps/web; fnm env --use-on-cd | Out-String | Invoke-Expression; fnm use 22.22.3; npm run typecheck- Passed
- Backend unit/compile:
cd apps/api; C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -m unittest app.test_runtime_policy app.test_session_turn_persistence engine_gateway.test_gateway_model20 tests OKpython -m compileall app engine_gateway- Passed
- Web production build:
cd apps/web; fnm use 22.22.3; npm run build- Passed
- Docker compose config:
cd infra- With dummy required env vars,
docker compose --env-file .env.example config --quiet - Passed
- Focused Playwright after fixes:
npx playwright test e2e/admin.spec.ts e2e/db-persistence.spec.ts e2e/voice-success.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run10 passed
- Admin manage-users focused rerun:
PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/admin.spec.ts --project=chromium-desktop --project=chromium-mobile --grep "manage real server-known users"2 passed
- Full Playwright:
cd apps/web; PLAYWRIGHT_HOST=127.0.0.1 npx playwright test72 passedafter public API/cloudflared was restored- 참고: tunnel을 내린 직후 첫 전체 실행은 public login gate 2개만
Cloudflare 1033으로 실패하고 나머지70 passed였다.scripts\start-public-runtime.ps1로 prod public API와 cloudflared를 복구한 뒤 전체 재실행에서72 passed.
- Public auth project discovery:
E2E_PUBLIC_AUTH=1 npx playwright test --list --project=chromium-public-auth2 tests: public production-safe config + real OAuth/turn
- Public production-safe gate:
E2E_PUBLIC_AUTH=1 npx playwright test e2e/public-auth-turn.spec.ts --project=chromium-public-auth --grep "production-safe"1 passedon rerun
- Admin/settings focused E2E:
npx playwright test e2e/admin.spec.ts e2e/settings.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run- Latest focused reruns:
admin.spec.ts8 passed,settings.spec.ts9 passed
- Auth/readiness focused E2E:
npx playwright test e2e/auth.spec.ts e2e/readiness.spec.ts --project=chromium-desktop --project=chromium-mobile14 passed
- Persona auth boundary:
- local unauth
http://127.0.0.1:8000/personas-> 401 - public unauth
https://api-vignette.chanpaca.net/personas-> 401
- local unauth
- Public runtime script:
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1- API 8001 health and public
https://api-vignette.chanpaca.net/healthboth returnedenvironment:"prod",db:true,engine:true
- Public web deployment:
wrangler pages deploy dist --project-name vignette --branch main --commit-hash 543d48c- Production deployment
0fe8f3cc https://vignette.chanpaca.net/servesassets/index-D1Z5DH5J.jsandassets/index-BA9-xxfn.csshttps://vignette.chanpaca.net/live2d/mao/Mao.model3.json, Haru model, and Cubism core routes return 404
- Public login screen:
npx playwright test e2e/auth.spec.ts --project=chromium-desktop --grep "public login"1 passed
- Local login on
127.0.0.1:5175:PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/auth.spec.ts --project=chromium-desktop6 passedPLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/auth.spec.ts --project=chromium-mobile6 passed- focused local dev-login redirect:
1 passed
- Learner home/history focused verification:
cd apps/web; npm run typecheck- Passed
PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/learner.spec.ts e2e/readiness.spec.ts --project=chromium-desktop --project=chromium-mobile14 passed- Visual audit screenshots:
apps/web/node_modules/.tmp/ui-audit/learn-empty-desktop.png,apps/web/node_modules/.tmp/ui-audit/learn-empty-mobile-compact.png - Screenshot metrics confirmed desktop/mobile document overflow
x:0, y:0, empty history visible, and real stats area present.
- Session dense viewport verification:
PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile8 passed
- Latest voice/session focused E2E:
PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/voice.spec.ts e2e/voice-success.spec.ts e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run15 passed
- Latest learner/session/settings/admin focused E2E:
PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/learner.spec.ts e2e/session-layout.spec.ts e2e/settings.spec.ts e2e/admin.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run37 passed
아직 못 끝낸 것
- 공개 Google OAuth 실제
/turnproof는 아직 없다.- 이유:
E2E_PUBLIC_STORAGE_STATE=apps/web/node_modules/.tmp/public-auth.json가 아직 확보되지 않았다. - 사용자가 Playwright codegen 브라우저에서 Google 로그인 후 storageState를 저장해야 한다.
- 2026-06-26 15:18 KST 기준
apps/web/node_modules/.tmp/public-auth.json은 없다. codegen 창/프로세스가 남아 있으면 로그인 완료 후 창을 닫아 저장을 완료하면 된다.
- 이유:
- 공개 API
environment:"dev"문제는 이번 세션에서 해결했다. cloudflared가 prod API인127.0.0.1:8001을 보도록 변경했고 public gate로 확인했다. - Google Console 설정은 브라우저 로그인 세션이 필요하다. 막히면 사용자가 로그인해 주기로 했다.
- 현재 worktree는 문서 상태 업데이트만 남아 있다. 관련 없는 사용자 변경을 되돌리지 말 것.
다음 세션 우선순위
- Google OAuth Console을 확인한다.
- Authorized redirect URI:
https://api-vignette.chanpaca.net/auth/callback - Authorized JavaScript origin:
https://vignette.chanpaca.net - 앱/도메인 설정은
hs.ac.kr,twentyoz.kr계정만 서버 claim 검증으로 허용한다. Google Console의 authorized domains와 이메일 도메인 제한은 다른 개념이다.
- Authorized redirect URI:
- storageState를 캡처하고 public
/turnsmoke를 실행한다.cd D:\workspace\vignette\apps\webnpx playwright codegen https://vignette.chanpaca.net/login --save-storage=.\node_modules\.tmp\public-auth.json$env:E2E_PUBLIC_AUTH="1"$env:E2E_PUBLIC_STORAGE_STATE=".\node_modules\.tmp\public-auth.json".\node_modules\.bin\playwright.cmd test e2e/public-auth-turn.spec.ts --project=chromium-public-auth
- UI/UX는 계속
docs/DESIGN_CONCEPT.md를 따른다.- learner home은 빈 사용자에게 fake 통계/기록을 보여주지 않는다. 0회 상태는 실제 서버 기록 0개로
기존 회기빈 상태를 보여주는 현재 구현을 유지한다. - active learning session은 전체 화면, 문서 스크롤 없음이 원칙이다.
- 실제 session history는
기존 회기,이어하기,기록/리뷰,다시 연습,새 회기 시작흐름으로 유지한다.
- learner home은 빈 사용자에게 fake 통계/기록을 보여주지 않는다. 0회 상태는 실제 서버 기록 0개로
- 다음 배포 전
admin/engine-config를 다시 확인한다.- 의도된 현재값이
claude_cli / http://127.0.0.1:9099 / gateway-default인지 확인. - E2E가 engine config를 건드리므로 배포 전 실제 운영값을 반드시 재확인한다.
- 의도된 현재값이
로컬 실행 명령
Engine gateway:
cd D:\workspace\vignette\apps\api
C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -m uvicorn engine_gateway.gateway:app --host 127.0.0.1 --port 9099
API:
cd D:\workspace\vignette\apps\api
$env:ENGINE_URL="http://127.0.0.1:9099"
$env:ENGINE_MODE="claude_cli"
$env:ENVIRONMENT="dev"
$env:AUTH_DEV_LOGIN_ENABLED="true"
C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port 8000
Public API process:
cd D:\workspace\vignette
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1
Web:
cd D:\workspace\vignette\apps\web
fnm env --use-on-cd | Out-String | Invoke-Expression
fnm use 22.22.3
npm run dev -- --host localhost --port 5173
최근 주요 변경 파일
apps/web/e2e/support.tsapps/web/e2e/auth.spec.tsapps/web/e2e/learner.spec.tsapps/web/e2e/session-layout.spec.tsapps/web/e2e/session-review.spec.tsapps/web/e2e/session-persistence.spec.tsapps/web/e2e/teacher.spec.tsapps/web/e2e/voice.spec.tsapps/web/e2e/voice-success.spec.tsapps/web/e2e/public-auth-turn.spec.tsapps/web/e2e/db-persistence.spec.tsapps/web/e2e/README.mdapps/web/playwright.config.tsapps/web/src/pages/Admin.tsxapps/web/src/pages/Login.tsxapps/web/src/pages/Session.tsxapps/web/src/pages/Settings.tsxapps/web/src/pages/session/session.cssapps/web/src/components/avatar/ClientAvatar.tsxapps/web/src/components/avatar/persona.tsapps/api/app/config.pyapps/api/app/main.pyapps/api/app/routes/admin.pyapps/api/app/routes/personas.pyapps/api/app/routes/users.pyapps/api/app/routes/voice.pyapps/api/app/test_session_turn_persistence.pyapps/api/app/test_runtime_policy.pyinfra/docker-compose.ymlinfra/.env.exampledocs/HANDOFF.mddocs/dev_dashboard.htmlapps/web/src/lib/api.ts