전수 E2E 순회·소유자 결정 6건 구현·디자인 감사 반영

- 전수 순회: IA 전 라우트 412개 기능 인벤토리를 체크리스트 백로그로 관리
  (docs/ops/e2e-full-sweep-2026-07-27.md), 신규 full-sweep 스펙 10파일 추가.
  RED→GREEN으로 결함 12건 수정: 관리자 무한 렌더 프리즈(TanStack autoReset
  루프), 복수 코호트 저장 유실, 페르소나 보관 503(SQL 컬럼 모호성), PII 과잉
  마스킹, /admin/ai watchdog 오탐 오버레이, 모바일 겹침 2건, 설정 스크롤
  스파이, 온보딩 전화번호 무검증, 리뷰 조사·난도 라벨, pending 피드백 등.
- 소유자 결정 구현: 설정 아바타 변경, 동의 철회·재동의 전체 흐름, 신규
  학습자 기초 우선 추천, 학생 분석 테이블 가상화(@tanstack/react-virtual),
  저작 모드 죽은 레일 정리, 감정 밸런스 타임라인 차트(deep turn_valence +
  결정론 파생 폴백).
- 디자인 감사(142차): 라이트 팔레트 AA 대비, 다크 토큰 별칭 통일, 미정의
  CSS 변수 정리, 한글 keep-all 전역화, 탭 타깃 24px, LCP preconnect.
- 검증: npm run e2e 병렬 432 수집 GREEN + 직렬 49/49 exit 0, 백엔드 pytest
  421, gateway 29, typecheck/build/design-ssot/dead-code/중복 게이트 통과,
  layout-visual-gate 15/15, session-layout 8/8. 상세는 SSOT 대시보드
  142~144차 노트.
This commit is contained in:
Yun Chan 2026-07-27 14:25:24 +09:00
parent 0ae94499f2
commit 4511383cd9
55 changed files with 9333 additions and 573 deletions

View file

@ -2595,120 +2595,6 @@ export default function PersonaStudio() {
className="ps-layout ps-authoring-layout"
data-authoring-step={authoringStep}
>
<aside className="ps-rail" aria-label="페르소나 목록">
<div className={surfaceClassName("ps-rail__head")}>
<div>
<Kicker></Kicker>
<b>{catalog.length + reviews.length}</b>
</div>
<Badge tone={loadState === "ready" ? "accent" : "neutral"}>
{loadState === "ready" ? suggestedCode : "..."}
</Badge>
</div>
<div
className={surfaceClassName("ps-workflow")}
aria-label="저작 단계"
>
<Kicker> </Kicker>
{AUTHORING_STEPS.map((step, index) => (
<div
className={`ps-workflow-step ${
index < authoringStepIndex
? "is-done"
: index === authoringStepIndex
? "is-current"
: ""
}`}
key={step.label}
>
<span>{index + 1}</span>
<div>
<b>{step.label}</b>
<small>{step.desc}</small>
</div>
</div>
))}
</div>
<div className="ps-list">
{reviews.length > 0 ? (
reviews.map((persona) => (
<button
type="button"
className={surfaceClassName(
`ps-list-row ${editingId === persona.persona_id ? "is-active" : ""}`,
{ variant: "interactive", flat: true },
)}
key={`${persona.persona_id}:${persona.version}`}
onClick={() => void loadDraft(persona.persona_id)}
disabled={busy !== null}
>
<span className="ps-list-row__code">{persona.code}</span>
<span className="ps-list-row__body">
<b>{persona.display_name}</b>
<small>
v{persona.version} ·{" "}
{personaReviewStatusLabel(persona.status)}
</small>
</span>
</button>
))
) : (
<EmptyState
title="검수 큐 없음"
desc="저장된 초안이 여기에 표시됩니다."
/>
)}
</div>
<div className={surfaceClassName("ps-approved")}>
<Kicker> </Kicker>
{catalog.length > 0 ? (
catalog.map((persona) => (
<article
className="ps-approved-row"
key={`${persona.code}:${persona.version ?? "approved"}`}
data-approved-persona-row="true"
>
<span className="ps-approved-row__code">
{persona.code}
</span>
<div className="ps-approved-row__body">
<b>{persona.display_name}</b>
<small>
v{persona.version ?? 1} ·{" "}
{persona.source === "database"
? "DB 공개본"
: persona.source}
</small>
</div>
<div className="ps-approved-row__actions">
<Button
size="sm"
variant="secondary"
onClick={() => void reviseApprovedPersona(persona)}
disabled={busy !== null || !persona.persona_id}
>
</Button>
<Button
size="sm"
variant="danger"
onClick={() => void archiveApprovedPersona(persona)}
disabled={busy !== null || !persona.persona_id}
>
</Button>
</div>
</article>
))
) : (
<span className="ps-muted"> </span>
)}
</div>
</aside>
<section className="ps-editor">
<div className={surfaceClassName("ps-editor__top")}>
<div>