모바일 메뉴와 주요 화면 UX 개선 및 시각 검수

This commit is contained in:
Yun Chan 2026-09-12 17:57:59 +09:00
parent 89b5093ec7
commit e8b770e4d9
50 changed files with 3189 additions and 457 deletions

View file

@ -293,14 +293,14 @@ test.describe("continuous improvement admin cockpit", () => {
await page.goto("/admin/continuous-improvement");
await expect(page.getByRole("heading", { name: "승격보다 근거를 먼저 본다" })).toBeVisible();
await expect(page.getByText("합성 콘텐츠 검증 파이프라인")).toBeVisible();
await expect(page.getByText("모델 변경 · 릴리스 게이트")).toBeVisible();
await expect(page.getByText("사건 회귀 DAG")).toBeVisible();
await expect(page.getByText("모니터 · 롤백 원장")).toBeVisible();
await expect(page.getByRole("heading", { name: "개선안 검토와 승인" })).toBeVisible();
await expect(page.getByText("생성 콘텐츠 검토")).toBeVisible();
await expect(page.getByText("모델 변경과 릴리스 검토")).toBeVisible();
await expect(page.getByText("문제 재발 방지 기록")).toBeVisible();
await expect(page.getByText("적용과 복구 이력")).toBeVisible();
await expect(page.getByText("롤백 실행 대기 · 실행기 미구성", { exact: true })).toBeVisible();
await expect(page.getByText("롤백 실행 실패", { exact: true })).toBeVisible();
await expect(page.getByText("롤백은 실행됐지만 verification 이벤트가 아직 없어")).toBeVisible();
await expect(page.getByText("복구는 실행됐지만 검증 기록이 아직 없습니다")).toBeVisible();
const contentCandidate = page.locator(
'[data-qualification-id="20000000-0000-4000-8000-000000000001"]',
);
@ -314,7 +314,7 @@ test.describe("continuous improvement admin cockpit", () => {
await expect(contentCandidate.getByRole("button", { name: "승인 기록" })).toBeDisabled();
await contentReason.fill("합성 경계와 수련 목표를 직접 검수함");
await contentReason.press("Enter");
await expect(contentCandidate.getByText("콘텐츠 승인 원장 기록됨")).toBeVisible();
await expect(contentCandidate.getByText("콘텐츠 승인 기록됨")).toBeVisible();
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
await page.getByTestId("continuous-improvement-cockpit").screenshot({
path: testInfo.outputPath("g8-continuous-improvement-before-approval.png"),
@ -325,7 +325,7 @@ test.describe("continuous improvement admin cockpit", () => {
await expect(missingGate.getByRole("radio", { name: "승인", exact: true })).toBeDisabled();
await expect(missingGate.getByRole("radio", { name: "보류", exact: true })).toBeEnabled();
await expect(missingGate.getByRole("radio", { name: "반려", exact: true })).toBeEnabled();
await expect(missingGate.getByText("필수 증거 4종 미완료로 승인 차단")).toBeVisible();
await expect(missingGate.getByText("필수 증거 4종이 갖춰지지 않아 승인할 수 없습니다")).toBeVisible();
await missingGate.screenshot({
path: testInfo.outputPath("g8-human-decision-approval-blocked.png"),
animations: "disabled",
@ -338,10 +338,10 @@ test.describe("continuous improvement admin cockpit", () => {
await modelGate.getByRole("radio", { name: "승인", exact: true }).check();
const approvalButton = modelGate.getByRole("button", { name: "승인 기록" });
await expect(approvalButton).toBeDisabled();
await expect(modelGate.getByText("승인 사유를 먼저 입력해야 ")).toBeVisible();
await expect(modelGate.getByText("승인 사유를 먼저 입력해야 합니다")).toBeVisible();
await approvalReason.fill("기준선과 rollback runbook을 독립 검토함");
await expect(approvalButton).toBeEnabled();
await expect(modelGate.getByText("승인 결정과 사유를 append-only 원장에 기록할 준비됨")).toBeVisible();
await expect(modelGate.getByText("승인 결정과 사유를 변경 이력에 기록할 준비가 되었습니다")).toBeVisible();
await modelGate.screenshot({
path: testInfo.outputPath("g8-human-decision-approval-ready.png"),
animations: "disabled",
@ -351,7 +351,7 @@ test.describe("continuous improvement admin cockpit", () => {
await approvalReason.focus();
await approvalReason.press("Enter");
await expect(modelGate.getByText("승격 승인 원장 기록됨")).toBeVisible();
await expect(modelGate.getByText("변경 승인 기록됨")).toBeVisible();
expect(mock.approvalBodies).toHaveLength(2);
expect(mock.approvalBodies[0]).toMatchObject({
target_kind: "content_qualification",
@ -388,14 +388,14 @@ test.describe("continuous improvement admin cockpit", () => {
const releaseGate = page.locator(`[data-gate-id="${RELEASE_GATE_ID}"]`);
const releaseApprove = releaseGate.getByRole("radio", { name: "승인", exact: true });
await expect(releaseApprove).toBeDisabled();
await expect(releaseGate.getByText("필수 증거 4종 미완료로 승인 차단")).toBeVisible();
await expect(releaseGate.getByText("필수 증거 4종이 갖춰지지 않아 승인할 수 없습니다")).toBeVisible();
await expect(releaseGate.getByRole("radio", { name: "보류", exact: true })).toBeEnabled();
await expect(releaseGate.getByRole("radio", { name: "반려", exact: true })).toBeEnabled();
await releaseGate.getByRole("radio", { name: "보류", exact: true }).check();
await releaseGate.getByLabel("사람 결정 사유").fill("rollback 증거 보강 뒤 다시 검토");
await releaseGate.getByRole("button", { name: "보류 기록" }).click();
await expect(releaseGate.getByText("보류 원장 기록됨")).toBeVisible();
await expect(releaseGate.getByText("보류 기록됨")).toBeVisible();
const contentCandidate = page.locator(
'[data-qualification-id="20000000-0000-4000-8000-000000000001"]',
@ -403,13 +403,13 @@ test.describe("continuous improvement admin cockpit", () => {
await contentCandidate.getByRole("radio", { name: "반려", exact: true }).check();
await contentCandidate.getByLabel("콘텐츠 결정 사유").fill("검수 기준을 충족하지 못한 후보");
await contentCandidate.getByRole("button", { name: "반려 기록" }).click();
await expect(contentCandidate.getByText("반려 원장 기록됨")).toBeVisible();
await expect(contentCandidate.getByText("반려 기록됨")).toBeVisible();
const modelGate = page.locator(`[data-gate-id="${MODEL_GATE_ID}"]`);
await modelGate.getByRole("radio", { name: "승인", exact: true }).check();
await modelGate.getByLabel("사람 결정 사유").fill("독립 증거 4종과 승격 조건을 확인함");
await modelGate.getByRole("button", { name: "승인 기록" }).click();
await expect(modelGate.getByText("승격 승인 원장 기록됨")).toBeVisible();
await expect(modelGate.getByText("변경 승인 기록됨")).toBeVisible();
expect(mock.approvalBodies).toHaveLength(3);
expect(mock.approvalBodies[0]).toMatchObject({
@ -436,16 +436,16 @@ test.describe("continuous improvement admin cockpit", () => {
test("shows empty and degraded states without opening an approval action", async ({ page }) => {
await installMock(page, { empty: true });
await page.goto("/admin/continuous-improvement");
await expect(page.getByText("검토할 콘텐츠 후보가 없")).toBeVisible();
await expect(page.getByText("대기 중인 게이트가 없어")).toBeVisible();
await expect(page.getByText("등록된 운영 사건이 없어")).toBeVisible();
await expect(page.getByText("검토할 콘텐츠 후보가 없습니다")).toBeVisible();
await expect(page.getByText("대기 중인 변경 검토가 없습니다")).toBeVisible();
await expect(page.getByText("등록된 운영 문제가 없습니다")).toBeVisible();
await expect(page.getByRole("button", { name: /승인 기록|결정 기록/ })).toHaveCount(0);
await page.unroute("**/api/**");
await installMock(page, { status: 503 });
await page.reload();
await expect(page.getByRole("heading", { name: "개선 원장을 확인할 수 없어" })).toBeVisible();
await expect(page.getByText(/승인 동작은 닫힌 상태/)).toBeVisible();
await expect(page.getByRole("heading", { name: "개선 기록을 확인할 수 없습니다" })).toBeVisible();
await expect(page.getByText(/승인 동작은 사용할 수 없습니다/)).toBeVisible();
await expectNoHorizontalOverflow(page);
});