docs: restore detailed dashboard tab content

This commit is contained in:
Yun Chan 2026-06-26 16:16:07 +09:00
parent 47c5b97a6d
commit cb2aebd76c

View file

@ -184,6 +184,9 @@
.detail-block h4 { margin:0 0 8px; font-size:13px; }
.detail-block ul { margin:0; padding-left:18px; color:var(--ink-2); font-size:12px; line-height:1.6; }
.detail-block li + li { margin-top:4px; }
.tab-subhead { margin:18px 0 10px; }
.tab-subhead h4 { margin:0; font-size:14px; line-height:1.35; }
.tab-subhead p { margin:4px 0 0; color:var(--ink-3); font-size:12px; }
code { font-family:var(--num); font-size:12px; background:var(--paper-2); padding:2px 6px; border-radius:6px; }
.wide { grid-column:1 / -1; }
.commands { display:grid; gap:10px; }
@ -215,7 +218,7 @@
<body>
<header>
<div class="brand"><span class="mark" aria-hidden="true"></span><span>Vignette 개발 대시보드</span></div>
<div class="stamp">2026-06-26 16:04 KST</div>
<div class="stamp">2026-06-26 16:15 KST</div>
</header>
<main>
@ -369,37 +372,121 @@
<div><b>검증</b><p>IRB 게이트가 아니라 데이터/SSO 게이트로 문서 전수 정합</p></div>
</div>
</div>
<div class="detail-blocks">
<div class="detail-block">
<h4>팀장 판정</h4>
<ul>
<li>로컬 회귀와 공개 prod-safe 게이트는 통과했다.</li>
<li>Cloudflare Pages production `0fe8f3cc`이 `543d48c` 소스로 배포됐다.</li>
<li>운영 반영 전 남은 핵심 증거는 공개 Google OAuth 실제 `/turn` smoke다.</li>
<li>런타임 mock/demo 자산은 운영 번들에서 제거하고, 테스트 fixture만 남긴다.</li>
</ul>
<div class="source-note">
<b>팀장 판정:</b> 로컬 회귀와 공개 prod-safe 게이트는 통과했다. Cloudflare Pages production `0fe8f3cc`이 `543d48c` 소스로 배포됐고, 운영 반영 전 남은 핵심 증거는 공개 Google OAuth 실제 `/turn` smoke다. 런타임 mock/demo 자산은 운영 번들에서 제거하고 테스트 fixture만 남긴다.
</div>
<div class="tab-subhead">
<h4>Phase 0 완료 상세 목록</h4>
<p>이전 “완료” 카드 내용을 탭 안에서 항목별로 보존한다.</p>
</div>
<div class="task-list">
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>DB persona 기반 테스트</b><p>`session-persistence`, `teacher`, `voice`는 `/api/personas`에서 실제 DB persona를 가져온다.</p></div>
<div><b>산출물</b><p>seed persona 직접 결합 제거, 실제 persona catalog 기반 E2E</p></div>
<div><b>검증</b><p>persona 관련 E2E가 DB-backed persona로 통과</p></div>
</div>
<div class="detail-block">
<h4>제품/UX 완료</h4>
<ul>
<li>학습자 홈 0회 기록 상태는 fake 기록 없이 실제 서버 세션 0개로 표시한다.</li>
<li>active session은 실제 SSE token 이벤트로 자막을 쌓는다.</li>
<li>텍스트/음성 실패 턴은 transcript와 저장소에 남기지 않는다.</li>
<li>375px급 active session viewport overflow를 보정했다.</li>
<li>직접 URL persona 우회는 DB 원본 persona가 아니면 차단한다.</li>
<li>세션 화면은 SVG persona avatar만 렌더링한다.</li>
</ul>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>비개발 런타임 보호</b><p>prod/staging에서 dev login, seed fallback, OAuth 누락, 기본 secret, localhost CORS를 거부한다.</p></div>
<div><b>산출물</b><p>fail-closed runtime config validation</p></div>
<div><b>검증</b><p>backend runtime policy unittest 통과</p></div>
</div>
<div class="detail-block">
<h4>운영/테스트 완료</h4>
<ul>
<li>seed persona 결합을 제거하고 DB persona 기반 E2E로 전환했다.</li>
<li>비개발 런타임은 dev login, seed fallback, 기본 secret, localhost CORS를 fail-closed 한다.</li>
<li>compose OAuth/CORS/OpenAI base 설정을 API 컨테이너에 명시했다.</li>
<li>persona catalog는 비로그인 401로 인증 경계를 가진다.</li>
<li>admin 모바일 overflow, AI 운영 상태 문구, global engine config 테스트 격리를 정리했다.</li>
<li>handoff, E2E README, 이 대시보드를 최신 상태로 맞췄다.</li>
</ul>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>compose OAuth 주입</b><p>`ENVIRONMENT`, Google OAuth vars, `CORS_ORIGINS`, `OPENAI_BASE_URL`을 API 컨테이너에 명시했다.</p></div>
<div><b>산출물</b><p>compose 환경 주입 정리</p></div>
<div><b>검증</b><p>`docker compose --env-file .env.example config --quiet` 통과</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>admin 모바일 overflow 수정</b><p>긴 readiness detail을 안정화했고, 사용자 변경은 DB 저장소가 durable할 때만 허용한다.</p></div>
<div><b>산출물</b><p>admin responsive layout, durable store gate, AI health copy</p></div>
<div><b>검증</b><p>admin manage-users desktop/mobile 2 passed</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>global engine config 테스트 분리</b><p>`db-persistence`는 전역 engine config를 바꾸므로 `@single-run`으로 분리했다.</p></div>
<div><b>산출물</b><p>Playwright single-run tagging</p></div>
<div><b>검증</b><p>전체 Playwright 72 passed</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>handoff와 문서 갱신</b><p>`docs/HANDOFF.md`, `docs/dev_dashboard.html`, `apps/web/e2e/README.md`를 최신 상태로 맞췄다.</p></div>
<div><b>산출물</b><p>handoff, dashboard, E2E README</p></div>
<div><b>검증</b><p>최신 커밋 기준 상태/검증/운영 포트 일치</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>공개 API prod 전환</b><p>`cloudflared` ingress를 `127.0.0.1:8001` prod API로 돌렸고 public readiness gate가 통과했다.</p></div>
<div><b>산출물</b><p>prod API 8001, tunnel config</p></div>
<div><b>검증</b><p>`https://api-vignette.chanpaca.net/health` prod/db/engine OK</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>공개 런타임 재기동 스크립트</b><p>`scripts/start-public-runtime.ps1`와 `scripts/stop-public-runtime.ps1`로 API 8001과 cloudflared를 재현 가능하게 만들었다.</p></div>
<div><b>산출물</b><p>public runtime start/stop scripts</p></div>
<div><b>검증</b><p>engine 9099 + API 8001 + cloudflared only</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>persona catalog 인증화</b><p>`/personas`는 이제 비로그인 401이다. 로컬과 공개 API 모두 직접 확인했고 E2E 회귀를 추가했다.</p></div>
<div><b>산출물</b><p>authenticated persona catalog boundary</p></div>
<div><b>검증</b><p>local/public unauth `GET /personas` 401</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>public API base fallback 보강</b><p>public 웹에서 `/api/auth/config`가 SPA HTML로 떨어지지 않도록 public API origin fallback을 추가했다.</p></div>
<div><b>산출물</b><p>public API origin fallback</p></div>
<div><b>검증</b><p>public login screen E2E 1 passed</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>학습자 홈 0회 기록 상태</b><p>학습자 홈은 fake 기록 없이 실제 서버 세션 0개를 `기존 회기` 빈 상태와 0 카운트로 표시한다.</p></div>
<div><b>산출물</b><p>empty history UX, zero count, no fake stats</p></div>
<div><b>검증</b><p>learner screenshots: document overflow 0, empty history visible</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>로컬 5175 로그인 재기동 검증</b><p>`127.0.0.1:5175` web, `8000` API, `9099` engine을 재기동하고 dev-login 서버 세션과 `/learn` redirect를 확인했다.</p></div>
<div><b>산출물</b><p>local dev-login flow proof</p></div>
<div><b>검증</b><p>desktop/mobile auth flow passed</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>active session dense viewport 보정</b><p>375px급 active session에서 stage 내부가 1px 넘게 잘리던 문제를 avatar/padding 조정으로 제거했다.</p></div>
<div><b>산출물</b><p>session layout responsive adjustment</p></div>
<div><b>검증</b><p>session-layout desktop/mobile 8 passed</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>텍스트 턴 SSE 전환</b><p>`POST /sessions/{id}/stream` token 이벤트로만 내담자 자막을 쌓고, HTTP 거절과 SSE error 모두 실패 턴을 transcript와 저장소에 남기지 않는다.</p></div>
<div><b>산출물</b><p>real SSE stream turn path, failed turn discard</p></div>
<div><b>검증</b><p>session layout/turn E2E passed</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>음성 실패 턴 미저장</b><p>음성 회기도 엔진 응답 생성 성공 후에만 learner/client turn을 저장한다. 실패한 transcript는 pending 상태에서 제거한다.</p></div>
<div><b>산출물</b><p>voice failure persistence guard</p></div>
<div><b>검증</b><p>voice + voice-success + session-layout 15 passed</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>비영구 상태 문구 정리</b><p>admin health와 voice degraded reason은 내부 fallback/seed 표현 대신 비영구 런타임 상태를 사용자용 문구로 표시한다.</p></div>
<div><b>산출물</b><p>operator-facing copy cleanup</p></div>
<div><b>검증</b><p>settings/admin focused E2E 통과</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>직접 URL persona 차단</b><p>`/learn/session/:code`로 직접 들어와도 DB 원본 persona가 아니면 회기 시작을 막는다.</p></div>
<div><b>산출물</b><p>route guard, degraded/seed fallback regression</p></div>
<div><b>검증</b><p>unavailable persona code E2E 통과</p></div>
</div>
<div class="task-row">
<div><span class="task-status s-done">DONE</span></div>
<div><b>demo Live2D 자산 제거</b><p>Mao/Haru 샘플과 Pixi/Cubism 런타임을 공개 배포물에서 제거했다. 세션 화면은 SVG persona avatar만 렌더링한다.</p></div>
<div><b>산출물</b><p>SVG-only avatar, legacy `/live2d/*` 404 function</p></div>
<div><b>검증</b><p>Mao/Haru/Cubism core routes 404, no Live2D/Pixi chunks</p></div>
</div>
</div>
</div>
@ -606,39 +693,51 @@
<div><b>검증</b><p>재기동 후 public health와 `/turn` smoke 재확인</p></div>
</div>
</div>
<div class="detail-blocks">
<div class="detail-block">
<h4>검증 실행 결과</h4>
<ul>
<li>Web typecheck: `npm run typecheck` passed.</li>
<li>Backend unittest: `app.test_runtime_policy`, `app.test_session_turn_persistence`, `engine_gateway.test_gateway_model` 20 OK.</li>
<li>Python compile: `python -m compileall app engine_gateway` passed.</li>
<li>Web build: `npm run build` passed.</li>
<li>Full E2E: `PLAYWRIGHT_HOST=127.0.0.1 npx playwright test` 72 passed.</li>
<li>Latest focused E2E: learner + session-layout + settings + admin 37 passed.</li>
</ul>
</div>
<div class="detail-block">
<h4>공개 운영 증거</h4>
<ul>
<li>Pages deploy: `0fe8f3cc`, source `543d48c`, branch `main`.</li>
<li>Custom domain assets: `index-D1Z5DH5J.js`, `index-BA9-xxfn.css`.</li>
<li>Public readiness: `public-auth-turn.spec.ts --grep production-safe` 1 passed.</li>
<li>Public login: `auth.spec.ts --grep public login` 1 passed.</li>
<li>Legacy Live2D routes: Mao/Haru/Cubism core 404.</li>
<li>Persona auth boundary: local/public unauth `GET /personas` 401.</li>
</ul>
</div>
<div class="detail-block">
<h4>다음 실행 명령</h4>
<ul>
<li>`npx playwright codegen https://vignette.chanpaca.net/login --save-storage=.\node_modules\.tmp\public-auth.json`</li>
<li>`$env:E2E_PUBLIC_AUTH="1"`</li>
<li>`$env:E2E_PUBLIC_STORAGE_STATE=".\node_modules\.tmp\public-auth.json"`</li>
<li>`.\node_modules\.bin\playwright.cmd test e2e/public-auth-turn.spec.ts --project=chromium-public-auth`</li>
<li>`powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1`</li>
</ul>
</div>
<div class="tab-subhead">
<h4>검증 실행 상세 표</h4>
<p>요약하지 않고 실행 범위, 명령, 결과를 행 단위로 남긴다.</p>
</div>
<table>
<thead><tr><th>범위</th><th>명령 / 확인 대상</th><th>결과</th></tr></thead>
<tbody>
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
<tr><td>Backend unittest</td><td><code>python -m unittest app.test_runtime_policy app.test_session_turn_persistence engine_gateway.test_gateway_model</code></td><td>20 tests OK</td></tr>
<tr><td>Python compile</td><td><code>python -m compileall app engine_gateway</code></td><td>Passed</td></tr>
<tr><td>Web build</td><td><code>npm run build</code></td><td>Passed</td></tr>
<tr><td>Pages production deploy</td><td><code>wrangler pages deploy dist --project-name vignette --branch main</code></td><td>`0fe8f3cc`, source `543d48c`</td></tr>
<tr><td>Custom domain assets</td><td><code>https://vignette.chanpaca.net/</code></td><td>`index-D1Z5DH5J.js`, `index-BA9-xxfn.css`</td></tr>
<tr><td>Legacy Live2D routes</td><td><code>/live2d/mao/* / /live2d/haru/* / /live2d/live2dcubismcore.min.js</code></td><td>404</td></tr>
<tr><td>Compose</td><td><code>docker compose --env-file .env.example config --quiet</code></td><td>Passed with dummy required env</td></tr>
<tr><td>Focused E2E</td><td><code>admin + db-persistence + voice-success</code></td><td>10 passed</td></tr>
<tr><td>Admin manage-users</td><td><code>admin.spec.ts --grep manage real server-known users</code></td><td>desktop/mobile 2 passed</td></tr>
<tr><td>Full E2E</td><td><code>PLAYWRIGHT_HOST=127.0.0.1 npx playwright test</code></td><td>72 passed</td></tr>
<tr><td>Public auth discovery</td><td><code>E2E_PUBLIC_AUTH=1 npx playwright test --list --project=chromium-public-auth</code></td><td>2 tests listed</td></tr>
<tr><td>Public readiness</td><td><code>public-auth-turn.spec.ts --grep production-safe</code></td><td>1 passed</td></tr>
<tr><td>Persona auth boundary</td><td><code>GET /personas</code></td><td>verified local/public unauth 401</td></tr>
<tr><td>Public login</td><td><code>auth.spec.ts --grep public login</code></td><td>1 passed</td></tr>
<tr><td>Public runtime script</td><td><code>scripts\start-public-runtime.ps1</code></td><td>engine 9099 + API 8001 + cloudflared only</td></tr>
<tr><td>Public API health</td><td><code>https://api-vignette.chanpaca.net/health</code></td><td>prod, db true, engine true</td></tr>
<tr><td>Local 5175 login</td><td><code>PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 auth.spec.ts</code></td><td>desktop/mobile passed</td></tr>
<tr><td>Learner/readiness E2E</td><td><code>learner.spec.ts + readiness.spec.ts desktop/mobile</code></td><td>14 passed</td></tr>
<tr><td>Learner screenshots</td><td><code>learn-empty-desktop.png / learn-empty-mobile-compact.png</code></td><td>document overflow 0, empty history visible</td></tr>
<tr><td>Session layout/turn</td><td><code>session-layout.spec.ts desktop/mobile</code></td><td>8 passed</td></tr>
<tr><td>Voice/session focused</td><td><code>voice + voice-success + session-layout</code></td><td>15 passed</td></tr>
<tr><td>Latest focused E2E</td><td><code>learner + session-layout + settings + admin</code></td><td>37 passed</td></tr>
</tbody>
</table>
<div class="tab-subhead">
<h4>다음 실행 명령</h4>
<p>남은 public OAuth `/turn` proof 확보용 명령이다.</p>
</div>
<div class="commands">
<pre>cd D:\workspace\vignette\apps\web
npx 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</pre>
<pre>cd D:\workspace\vignette
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1</pre>
</div>
</div>