From a213479e8e895ed3d290da9c9cf4596b811524dd Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Mon, 3 Aug 2026 17:02:28 +0900 Subject: [PATCH] =?UTF-8?q?feat(ai-character):=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=98=A4=EB=94=94=EC=98=A4=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=20UI=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DESIGN.md | 7 +- docs/20260725_AI캐릭터관리자웹/plan-task.md | 129 +++++++++++++++++- docs/20260725_AI캐릭터관리자웹/prd.md | 8 ++ .../ui/__tests__/admin-audio-player.test.tsx | 26 +++- src/shared/ui/admin-audio-player.tsx | 32 ++--- 5 files changed, 179 insertions(+), 23 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index eba3e5c..dcc92f3 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -175,9 +175,12 @@ Primary font stack: `Pretendard`, `Noto Sans KR`, `Apple SD Gothic Neo`, `system ### AdminAudioPlayer -- Structure: native audio element wrapped with play/pause, seek, time, volume, speed, generic error, and manual retry controls. +- Reference: [Plyr audio](https://plyr.io/#audio) is the primary surface reference; [Media Chrome audio](https://www.media-chrome.org/docs/en/audio-player) supplies the explicit current/duration and playback-rate anatomy. Runtime extraction on 2026-08-03 found 44–52px single-row bars, icon-only media controls, a flexible progress range, compact time/rate values, and no persistent descriptive labels. +- Structure: native audio element wrapped by one compact control bar ordered play/pause → flexible seek → current/duration → playback rate → volume icon/range, followed by the existing generic error and manual retry block. No image, poster, video viewport, waveform, or settings menu is rendered. +- Surface: `card`, `border`, `input`, `primary`, `primary-foreground`, and existing radius/spacing tokens only. The standard bar is approximately one 44px control high with 4–12px token spacing; remaining inline space goes to seek before volume. - Variants: shared signed-URL player only; it never downloads, autoplays, auto-refetches, or infers signed URL expiry. -- Accessibility: player region is named by title, keyboard Space/Enter toggles play, seek/volume use range inputs, speed uses native select. +- Responsive: 375px, 768px, and 1280px standard viewports keep a single visual bar. Only constrained containers at 200% zoom may wrap secondary controls; clipping and horizontal overflow are forbidden. +- Accessibility: player region is named by title, keyboard Space/Enter toggles play, seek/volume use range inputs, speed uses native select, and icon-only controls retain accessible names. Visible `볼륨` and `재생 속도` labels are omitted to match the compact reference while labels remain available to assistive technology. - Motion: none. ### AudioPlaybackProvider diff --git a/docs/20260725_AI캐릭터관리자웹/plan-task.md b/docs/20260725_AI캐릭터관리자웹/plan-task.md index 9f8ceb0..2cf6969 100644 --- a/docs/20260725_AI캐릭터관리자웹/plan-task.md +++ b/docs/20260725_AI캐릭터관리자웹/plan-task.md @@ -15,6 +15,7 @@ | 재작성일 | 2026-07-26 | | 요구사항 기준 | [prd.md](./prd.md) | | API 기준 | [api-contract.openapi.json](./api-contract.openapi.json) | +| 현재 활성 Goal | 없음 (`P4-R9` 완료) | ## 목표 @@ -2438,6 +2439,130 @@ field와 수정 금지 control이 실제 network 요청·화면에 일치하는 - REFACTOR/회귀: 기존 success/progress/415/malformed response/cancel retry contract를 유지했다. reviewer blocker로 fetch-first 순서 회귀를 추가한 뒤 `auth` 주입 시 storage fallback을 쓰지 않도록 보완했다. `npm run test:run -- src/shared/api/__tests__/client-auth.test.ts src/features/audio-contents/tests/audio-upload.test.ts src/features/audio-contents/tests/audio-upload-auth-lifecycle.test.ts` — 3 files / 22 tests passed. `npm run test:run -- src/features/audio-contents` — 9 files / 58 tests passed. `npm run test:run -- src/app src/features/auth src/shared/api src/features/audio-contents/tests/audio-upload.test.ts` — 13 files / 91 tests passed. `npm run test:run` — 79 files / 397 tests passed. `npm run typecheck`, `npm run lint`, `npm run build:dev`, `npm run build:prod`, targeted `git diff --check` — 모두 exit 0. build는 기존 503.04kB chunk warning만 표시했다. LSP diagnostics는 변경 파일·디렉터리 기준 오류 0건이었다. - E2E/수동 확인: 사용자 지시에 따라 개발 중 E2E는 반복 실행하지 않고 모든 Task 구현 뒤 필요 시 수행한다. +### Task R4.9 — 공통 오디오 플레이어 compact UI + +**Goal 실행 `P4-R9`:** 공통 `AdminAudioPlayer`를 image·video 영역 없는 compact audio-only UI로 개선해 오디오 콘텐츠 목록·상세와 커뮤니티 목록·Sheet에 동일하게 적용한다. + +- **Reference packet:** `.omo/evidence/p4-r9-reference/plyr-audio-{375,768,1280}.png`, `.omo/evidence/p4-r9-reference/media-chrome-audio-{375,768,1280}.png`. Plyr의 밝은 52px compact bar를 primary surface로, Media Chrome의 시간·배속·음량 control anatomy를 secondary structure로 사용한다. + +- **시작 조건:** PRD `AUDIO-021`, `AUDIO-026`, `AUDIO-034`와 기존 `AdminAudioPlayer`·`AudioPlaybackProvider` 단일 재생 계약. +- **완료 증거:** 아래 RED/GREEN/REFACTOR 체크박스, focused·전체 unit·mock E2E·typecheck·lint·build 통과, Chromium/mobile Chrome 실제 화면 screenshot과 visual QA 판정, QA resource teardown 기록. +- **범위 밖:** 새 audio player dependency, waveform, playlist, download control, cover/poster/video viewport, API·DTO·signed URL lifecycle 변경. + +**Files:** + +- Modify: `src/shared/ui/admin-audio-player.tsx` +- Modify: `src/shared/ui/__tests__/admin-audio-player.test.tsx` +- Verify unchanged: `src/features/audio-contents/components/AudioContentList.tsx` +- Verify unchanged: `src/features/audio-contents/components/AudioContentListItem.tsx` +- Verify unchanged: `src/features/audio-contents/pages/AudioContentDetailPage.tsx` +- Verify unchanged: `src/features/community-posts/components/CommunityPostList.tsx` +- Verify unchanged: `src/features/community-posts/components/CommunityPostListItem.tsx` +- Verify unchanged: `src/features/community-posts/components/CommunityPostSheet.tsx` +- Test: `src/features/audio-contents/tests/audio-list.test.tsx` +- Test: `src/features/audio-contents/tests/audio-player.test.tsx` +- Test: `src/features/community-posts/tests/community-list.test.tsx` +- Test: `src/features/community-posts/tests/community-sheet.test.tsx` +- E2E: `tests/e2e/audio-content.spec.ts` +- E2E: `tests/e2e/community.spec.ts` + +**Interfaces:** + +- Consumes: `AdminAudioPlayerProps { playerId, src, title }`, `useAudioPlayback`, native `HTMLAudioElement` events. +- Produces: 기존 props와 재생 동작을 바꾸지 않는 compact audio-only control; `재생 진행`과 `재생 설정` accessible group. + +**시나리오 계약:** + +| 시나리오 | 이진 통과 조건 | 실제 surface | 자동 test | +|---|---|---|---| +| Happy path | 오디오 목록에서 재생 버튼을 누르면 버튼 이름이 `일시정지`로 바뀌고, 상시 설명 label 없는 재생·진행 slider·현재/전체 시간·배속·음량 control이 표준 viewport에서 Plyr/Media Chrome처럼 한 줄에 표시된다. | mock Chromium `/ai-characters/101/audio-contents`에서 `page.click({ name: "재생" })` 후 reference-fidelity screenshot | `src/shared/ui/__tests__/admin-audio-player.test.tsx`의 `AdminAudioPlayer matches the compact reference control anatomy` | +| Edge | 320px viewport와 200% zoom에서 player의 `scrollWidth <= clientWidth`이고 모든 control이 viewport 안에 있다. | mock mobile Chrome 목록·상세 screenshot과 overflow probe | `tests/e2e/audio-content.spec.ts`의 기존 320px·200% zoom player 시나리오 | +| Adjacent regression | 커뮤니티 목록과 Sheet가 같은 player 구조를 사용하고 두 player 중 새 항목 재생 시 기존 항목이 pause되며 오류·수동 재시도 계약이 유지된다. | mock Chromium `/ai-characters/101/community-posts` 목록·Sheet 재생과 screenshot | `src/shared/ui/__tests__/admin-audio-player.test.tsx`, `src/features/community-posts/tests/community-sheet.test.tsx`, `tests/e2e/community.spec.ts` | + +**TDD 절차:** + +- [x] **RED: 실패 테스트 작성/실패 확인** — `src/shared/ui/__tests__/admin-audio-player.test.tsx`에 다음 reference-anatomy test를 작성한다. + + production code를 수정하기 전에 mock Chromium의 오디오 콘텐츠 목록·상세와 커뮤니티 목록·Sheet 현재 화면을 reference screenshot으로 저장한다. + + ```tsx + test("AdminAudioPlayer matches the compact reference control anatomy", () => { + render(); + + const player = screen.getByRole("group", { name: "샘플 오디오 오디오 플레이어" }); + const controls = within(player).getByRole("group", { name: "재생 제어" }); + expect(within(controls).getByRole("button", { name: "재생" })).toBeInTheDocument(); + expect(within(controls).getByRole("slider", { name: "재생 위치" })).toBeInTheDocument(); + expect(within(controls).getByRole("combobox", { name: "재생 속도" })).toBeInTheDocument(); + expect(within(controls).getByRole("slider", { name: "볼륨" })).toBeInTheDocument(); + expect(within(player).queryByText("볼륨")).not.toBeInTheDocument(); + expect(within(player).queryByText("재생 속도")).not.toBeInTheDocument(); + expect(player.querySelector("img")).not.toBeInTheDocument(); + expect(player.querySelector("video")).not.toBeInTheDocument(); + }); + ``` + + `npm run test:run -- src/shared/ui/__tests__/admin-audio-player.test.tsx -t "compact reference control anatomy"` 실행 시 `재생 제어` group을 찾지 못해 실패해야 한다. + +- [x] **GREEN: 최소 구현/통과 확인** — `src/shared/ui/admin-audio-player.tsx`의 기존 state·handler·native `