391 lines
21 KiB
HTML
391 lines
21 KiB
HTML
<!doctype html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Vignette 개발 대시보드</title>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/variable/pretendardvariable.min.css">
|
|
<style>
|
|
:root {
|
|
--paper:#FBFAF8;
|
|
--paper-2:#F4F2EE;
|
|
--surface:#FFFFFF;
|
|
--hair:#E5E1DA;
|
|
--ink:#1C2A2A;
|
|
--ink-2:#5A6663;
|
|
--ink-3:#93A09C;
|
|
--accent:#3E7A6E;
|
|
--accent-deep:#316257;
|
|
--accent-tint:#EEF4F2;
|
|
--clay:#B0735C;
|
|
--pos:#2C6E49;
|
|
--warn:#8A5A00;
|
|
--crit:#A8352C;
|
|
--pos-bg:#E8F3EC;
|
|
--warn-bg:#FAF1E0;
|
|
--crit-bg:#FAEAE8;
|
|
--info-bg:#EAF1F6;
|
|
--font:"Pretendard Variable", Pretendard, -apple-system, "Apple SD Gothic Neo", "Noto Sans KR", system-ui, sans-serif;
|
|
--num:"Pretendard Variable", "JetBrains Mono", ui-monospace, monospace;
|
|
--radius:8px;
|
|
}
|
|
* { box-sizing:border-box; }
|
|
body {
|
|
margin:0;
|
|
background:var(--paper);
|
|
color:var(--ink);
|
|
font-family:var(--font);
|
|
line-height:1.55;
|
|
letter-spacing:0;
|
|
}
|
|
header {
|
|
position:sticky;
|
|
top:0;
|
|
z-index:2;
|
|
display:flex;
|
|
align-items:center;
|
|
justify-content:space-between;
|
|
gap:24px;
|
|
min-height:58px;
|
|
padding:0 32px;
|
|
background:rgba(255,255,255,.92);
|
|
border-bottom:1px solid var(--hair);
|
|
backdrop-filter:blur(10px);
|
|
}
|
|
.brand { display:flex; align-items:center; gap:10px; font-weight:760; }
|
|
.mark { width:24px; height:24px; border:2px solid var(--accent); border-radius:50%; position:relative; }
|
|
.mark:after { content:""; width:6px; height:6px; border-radius:50%; background:var(--clay); position:absolute; left:7px; top:6px; }
|
|
.stamp { color:var(--ink-3); font:600 12px/1 var(--num); }
|
|
main { width:min(1180px, calc(100% - 40px)); margin:0 auto; padding:42px 0 64px; }
|
|
.hero { display:grid; grid-template-columns:minmax(0,1.2fr) minmax(280px,.8fr); gap:28px; align-items:start; margin-bottom:34px; }
|
|
.kicker { display:flex; align-items:center; gap:8px; color:var(--accent); font:700 12px/1.3 var(--num); letter-spacing:.08em; text-transform:uppercase; }
|
|
.kicker:before { content:""; width:6px; height:6px; border-radius:50%; background:var(--accent); }
|
|
h1 { margin:12px 0 0; font-size:30px; line-height:1.28; letter-spacing:0; }
|
|
h1 em { color:var(--accent-deep); font-style:normal; }
|
|
.lead { margin:12px 0 0; color:var(--ink-2); max-width:760px; font-size:15px; }
|
|
.summary {
|
|
display:grid;
|
|
grid-template-columns:repeat(2,minmax(0,1fr));
|
|
gap:1px;
|
|
overflow:hidden;
|
|
border:1px solid var(--hair);
|
|
border-radius:12px;
|
|
background:var(--hair);
|
|
}
|
|
.metric { min-width:0; padding:18px; background:var(--surface); }
|
|
.metric span { display:block; color:var(--ink-3); font-size:12px; font-weight:650; }
|
|
.metric b { display:block; margin-top:6px; color:var(--ink); font:760 28px/1 var(--num); }
|
|
section { margin-top:30px; }
|
|
.section-head { display:flex; align-items:end; justify-content:space-between; gap:18px; margin-bottom:14px; }
|
|
.section-head h2 { margin:0; font-size:20px; line-height:1.3; }
|
|
.section-head p { margin:4px 0 0; color:var(--ink-3); font-size:13px; }
|
|
.grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:16px; }
|
|
.panel {
|
|
min-width:0;
|
|
background:var(--surface);
|
|
border:1px solid var(--hair);
|
|
border-radius:12px;
|
|
padding:20px;
|
|
box-shadow:0 1px 2px rgba(28,42,42,.05);
|
|
}
|
|
.panel h3 { margin:0 0 10px; font-size:16px; line-height:1.35; }
|
|
.panel p { margin:0; color:var(--ink-2); font-size:13px; }
|
|
.tag { display:inline-flex; align-items:center; gap:6px; margin-bottom:12px; padding:3px 9px; border-radius:999px; font-size:12px; font-weight:680; }
|
|
.tag:before { content:""; width:7px; height:7px; border-radius:50%; }
|
|
.done { color:var(--pos); background:var(--pos-bg); }
|
|
.done:before { background:var(--pos); }
|
|
.doing { color:var(--warn); background:var(--warn-bg); }
|
|
.doing:before { background:var(--warn); }
|
|
.risk { color:var(--crit); background:var(--crit-bg); }
|
|
.risk:before { background:var(--crit); }
|
|
.info { color:#2E5A7A; background:var(--info-bg); }
|
|
.info:before { background:#3C7299; }
|
|
.list { display:grid; gap:10px; margin:0; padding:0; list-style:none; }
|
|
.list li { display:grid; grid-template-columns:10px minmax(0,1fr); gap:10px; color:var(--ink-2); font-size:13px; }
|
|
.list li:before { content:""; width:6px; height:6px; margin-top:8px; border-radius:50%; background:var(--accent); }
|
|
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; }
|
|
pre { margin:0; overflow:auto; padding:14px; border-radius:8px; background:#1E2724; color:#F6F0EC; font:12px/1.6 var(--num); }
|
|
table { width:100%; border-collapse:collapse; background:var(--surface); border:1px solid var(--hair); border-radius:12px; overflow:hidden; display:table; }
|
|
th,td { padding:12px 14px; border-bottom:1px solid var(--hair); text-align:left; vertical-align:top; font-size:13px; }
|
|
th { color:var(--ink-3); font-weight:700; background:var(--paper-2); }
|
|
tr:last-child td { border-bottom:0; }
|
|
@media (max-width:900px) {
|
|
header { padding:0 20px; }
|
|
main { width:min(100% - 28px, 1180px); padding-top:28px; }
|
|
.hero { grid-template-columns:1fr; }
|
|
.grid { grid-template-columns:1fr; }
|
|
}
|
|
@media (max-width:520px) {
|
|
.summary { grid-template-columns:1fr; }
|
|
h1 { font-size:24px; }
|
|
.stamp { display:none; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<div class="brand"><span class="mark" aria-hidden="true"></span><span>Vignette 개발 대시보드</span></div>
|
|
<div class="stamp">2026-06-26 15:18 KST</div>
|
|
</header>
|
|
|
|
<main>
|
|
<div class="hero">
|
|
<div>
|
|
<div class="kicker">team lead review</div>
|
|
<h1>Phase 0는 <em>검증 가능한 운영 안전장치와 E2E 안정화</em>까지 마무리됐다.</h1>
|
|
<p class="lead">
|
|
이번 세션은 seed persona 결합 제거, 비개발 fail-closed 설정, compose OAuth 주입,
|
|
admin 모바일 overflow 수정, persona 인증 경계 보강, 전체 Playwright 재통과, 공개 API prod 전환까지 완료했다.
|
|
이후 로컬 5175 런타임 재기동, admin 사용자 관리 E2E 안정화, 학습자 홈 0회 기록 UX,
|
|
active session 실제 SSE 스트림 전환, 텍스트/음성 실패 턴 미저장, 직접 URL persona 우회 차단, Live2D demo 자산 제거,
|
|
AI 운영 fail-closed 보강까지 전체 Playwright로 확인했다.
|
|
2026-06-26 15:18 KST 기준 공개 API 터널을 복구한 뒤 전체 Playwright 72개가 desktop/mobile/single-run 모두 통과했다.
|
|
Cloudflare Pages production 배포 `0fe8f3cc`도 완료했고, 커스텀 도메인이 새 asset을 서빙하는 것을 확인했다.
|
|
과거 Mao/Haru Live2D demo 자산은 repo와 번들에서 제거했고, `/live2d/*` 운영 경로는 404로 차단했다.
|
|
로컬 프로세스는 prod API 8001, engine gateway 9099, cloudflared tunnel 1개만 남기고 dev API와 임시 Vite preview를 내렸다.
|
|
공개 Google OAuth 실제 `/turn` 증명만 storageState 캡처 후 남아 있다.
|
|
</p>
|
|
</div>
|
|
<div class="summary" aria-label="현재 검증 요약">
|
|
<div class="metric"><span>Full Playwright</span><b>72 passed</b></div>
|
|
<div class="metric"><span>Pages Production</span><b>0fe8f3cc</b></div>
|
|
<div class="metric"><span>Public API</span><b>prod OK</b></div>
|
|
<div class="metric"><span>Backend unittest</span><b>20 OK</b></div>
|
|
<div class="metric"><span>App Commit</span><b>543d48c</b></div>
|
|
<div class="metric"><span>Decision</span><b>조건부 GO</b></div>
|
|
</div>
|
|
</div>
|
|
|
|
<section>
|
|
<div class="section-head">
|
|
<div>
|
|
<h2>팀장 판정</h2>
|
|
<p>이 HTML만 보고 판단해야 하는 항목</p>
|
|
</div>
|
|
</div>
|
|
<div class="grid">
|
|
<article class="panel">
|
|
<span class="tag done">go</span>
|
|
<h3>로컬 회귀와 공개 prod-safe 게이트 통과</h3>
|
|
<p>백엔드 unittest 20개, Python compile, 웹 production build, 전체 Playwright 72개가 통과했다. 공개 API도 `environment:"prod"`, `db:true`, `engine:true`로 응답한다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">deployed</span>
|
|
<h3>커스텀 도메인 운영 반영 완료</h3>
|
|
<p>Cloudflare Pages production `0fe8f3cc`이 `543d48c`로 배포됐고, `https://vignette.chanpaca.net`이 `index-D1Z5DH5J.js`와 `index-BA9-xxfn.css`를 서빙한다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag doing">conditional</span>
|
|
<h3>운영 반영 전 남은 증거 1개</h3>
|
|
<p>실제 Google 로그인 쿠키가 필요한 공개 `/turn` smoke는 아직 storageState가 없어 미완이다. 로그인 가능한 계정으로 한 번 캡처하면 바로 검증 가능하다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag info">mock audit</span>
|
|
<h3>런타임 목업 제거 기준</h3>
|
|
<p>프론트 페이지는 실제 API/DB 경로를 탄다. 남은 `mock` 표기는 테스트 코드의 `unittest.mock` 또는 의도적 fixture이며, seed fallback은 prod/staging에서 fail-closed 된다.</p>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<div class="section-head">
|
|
<div>
|
|
<h2>완료</h2>
|
|
<p>새 세션에서 반복하지 않아도 되는 작업</p>
|
|
</div>
|
|
</div>
|
|
<div class="grid">
|
|
<article class="panel">
|
|
<span class="tag done">E2E persona decoupled</span>
|
|
<h3>DB persona 기반 테스트</h3>
|
|
<p>`session-persistence`, `teacher`, `voice`는 `/api/personas`에서 실제 DB persona를 가져온다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">fail-closed</span>
|
|
<h3>비개발 런타임 보호</h3>
|
|
<p>prod/staging에서 dev login, seed fallback, OAuth 누락, 기본 secret, localhost CORS를 거부한다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">infra</span>
|
|
<h3>compose OAuth 주입</h3>
|
|
<p>`ENVIRONMENT`, Google OAuth vars, `CORS_ORIGINS`, `OPENAI_BASE_URL`을 API 컨테이너에 명시했다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">admin UX</span>
|
|
<h3>모바일 overflow 수정</h3>
|
|
<p>긴 readiness detail을 안정화했고, 사용자 변경은 DB 저장소가 durable할 때만 허용한다. AI 운영은 실제 어댑터명과 health 상태를 표시한다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">test isolation</span>
|
|
<h3>global engine config 분리</h3>
|
|
<p>`db-persistence`는 전역 engine config를 바꾸므로 `@single-run`으로 분리했다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">docs</span>
|
|
<h3>handoff 갱신</h3>
|
|
<p>`docs/HANDOFF.md`, `docs/dev_dashboard.html`, `apps/web/e2e/README.md`를 최신 상태로 맞췄다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">public prod</span>
|
|
<h3>공개 API prod 전환</h3>
|
|
<p>`cloudflared` ingress를 `127.0.0.1:8001` prod API로 돌렸고 public readiness gate가 통과했다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">ops script</span>
|
|
<h3>공개 런타임 재기동</h3>
|
|
<p>`scripts/start-public-runtime.ps1`와 `scripts/stop-public-runtime.ps1`로 API 8001과 cloudflared를 재현 가능하게 만들었다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">auth boundary</span>
|
|
<h3>persona catalog 인증화</h3>
|
|
<p>`/personas`는 이제 비로그인 401이다. 로컬과 공개 API 모두 직접 확인했고 E2E 회귀를 추가했다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">public web</span>
|
|
<h3>API base fallback 보강</h3>
|
|
<p>public 웹에서 `/api/auth/config`가 SPA HTML로 떨어지지 않도록 public API origin fallback을 추가했다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">learner UX</span>
|
|
<h3>0회 기록 상태 보강</h3>
|
|
<p>학습자 홈은 이제 fake 기록 없이 실제 서버 세션 0개를 `기존 회기` 빈 상태와 0 카운트로 표시한다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">local login</span>
|
|
<h3>5175 로컬 재기동 검증</h3>
|
|
<p>`127.0.0.1:5175` web, `8000` API, `9099` engine을 재기동하고 dev-login 서버 세션과 `/learn` redirect를 확인했다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">session UX</span>
|
|
<h3>dense viewport 보정</h3>
|
|
<p>375px급 active session에서 stage 내부가 1px 넘게 잘리던 문제를 avatar/padding 조정으로 제거했다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">real stream</span>
|
|
<h3>텍스트 턴 SSE 전환</h3>
|
|
<p>`POST /sessions/{id}/stream` token 이벤트로만 내담자 자막을 쌓고, HTTP 거절과 SSE error 모두 실패 턴을 transcript와 저장소에 남기지 않는다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">voice turn</span>
|
|
<h3>음성 실패 턴 미저장</h3>
|
|
<p>음성 회기도 엔진 응답 생성 성공 후에만 learner/client turn을 저장한다. 실패한 transcript는 pending 상태에서 제거한다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">ops copy</span>
|
|
<h3>비영구 상태 문구 정리</h3>
|
|
<p>admin health와 voice degraded reason은 내부 fallback/seed 표현 대신 비영구 런타임 상태를 사용자용 문구로 표시한다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">route guard</span>
|
|
<h3>직접 URL persona 차단</h3>
|
|
<p>`/learn/session/:code`로 직접 들어와도 DB 원본 persona가 아니면 회기 시작을 막는다. degraded/seed fallback 회귀를 추가했다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag done">avatar</span>
|
|
<h3>demo Live2D 자산 제거</h3>
|
|
<p>Mao/Haru 샘플과 Pixi/Cubism 런타임을 공개 배포물에서 제거했다. 세션 화면은 SVG persona avatar만 렌더링한다.</p>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<div class="section-head">
|
|
<div>
|
|
<h2>검증</h2>
|
|
<p>마지막으로 직접 실행한 결과</p>
|
|
</div>
|
|
</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</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>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>
|
|
</section>
|
|
|
|
<section>
|
|
<div class="section-head">
|
|
<div>
|
|
<h2>남은 리스크</h2>
|
|
<p>다음 세션 첫 작업</p>
|
|
</div>
|
|
</div>
|
|
<div class="grid">
|
|
<article class="panel">
|
|
<span class="tag risk">blocker</span>
|
|
<h3>public `/turn` proof 없음</h3>
|
|
<p>2026-06-26 15:18 KST 기준 Google 로그인 storageState가 없어 공개 API `/turn` 실제 성공 증거가 아직 없다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag doing">ops</span>
|
|
<h3>공개 운영 프로세스 유지</h3>
|
|
<p>현재 실행 프로세스는 `127.0.0.1:9099` engine gateway, `127.0.0.1:8001` prod API, cloudflared tunnel 1개다. 재부팅 후에는 engine gateway와 prod API, cloudflared를 같이 재기동해야 한다.</p>
|
|
</article>
|
|
<article class="panel">
|
|
<span class="tag doing">check before deploy</span>
|
|
<h3>engine config 재확인</h3>
|
|
<p>E2E가 admin engine config를 건드린다. 배포 전 의도한 model/url/mode인지 다시 확인해야 한다.</p>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<div class="section-head">
|
|
<div>
|
|
<h2>다음 명령</h2>
|
|
<p>공개 OAuth smoke</p>
|
|
</div>
|
|
</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>
|
|
</section>
|
|
|
|
<section>
|
|
<div class="section-head">
|
|
<div>
|
|
<h2>주의</h2>
|
|
<p>작업 방식</p>
|
|
</div>
|
|
</div>
|
|
<ul class="list">
|
|
<li>`frontenddesign` 스킬은 현재 세션에 없었다. 다음 세션에서 보이면 먼저 사용하고, 없으면 `docs/DESIGN_CONCEPT.md`를 따른다.</li>
|
|
<li>학습자 홈과 active session UX는 사용자가 강하게 불만을 말한 영역이다. fake 기록/통계 금지, 0회 기록은 실제 서버 세션 0개 기반 빈 상태로 유지, active session 전체화면/무문서스크롤 유지.</li>
|
|
<li>worktree는 원래부터 dirty다. 관련 없는 변경을 되돌리지 말 것.</li>
|
|
<li>OAuth secret은 출력하지 말 것. 실제 secret은 환경 파일/인프라 설정에만 둔다.</li>
|
|
</ul>
|
|
</section>
|
|
</main>
|
|
</body>
|
|
</html>
|