feat(ai-character): 오디오 콘텐츠 폼 입력 UX 정렬

This commit is contained in:
Yu Sung
2026-08-04 12:20:04 +09:00
parent a213479e8e
commit b62c416fa1
13 changed files with 608 additions and 173 deletions

View File

@@ -10,12 +10,12 @@
| 문서 항목 | 내용 |
|---|---|
| 상태 | 2026-08-01 `P9-R18`~`P9-R19`, `P10-R16`~`P10-R17` 자동 보완 완료, 실제 crop pixel·stale ADMIN·Series/FanTalk/Comments/file policy 수동 QA 대기 |
| 상태 | 2026-08-04 `P4-R10` Audio form UX 회귀 자동 검증 완료. `P9-R18`~`P9-R19`, `P10-R16`~`P10-R17` 자동 보완 완료, 실제 crop pixel·stale ADMIN·Series/FanTalk/Comments/file policy 수동 QA 대기 |
| 최초 작성일 | 2026-07-25 |
| 재작성일 | 2026-07-26 |
| 요구사항 기준 | [prd.md](./prd.md) |
| API 기준 | [api-contract.openapi.json](./api-contract.openapi.json) |
| 현재 활성 Goal | 없음 (`P4-R9` 완료) |
| 현재 활성 Goal | `P4-R10` 자동 검증 완료 (`P4-R9` 완료) |
## 목표
@@ -25,7 +25,7 @@ ADMIN이 로그인한 뒤 AI 캐릭터를 선택하고, 선택한 캐릭터 문
| 구분 | 현재 상태 | 남은 조건 |
|---|---|---|
| 자동 검증 범위 | `P9-R18`~`P9-R19`, `P10-R16`~`P10-R17` current-state contract 자동 보완 완료 | 실제 crop pixel·stale ADMIN·개발 API 수동 QA |
| 자동 검증 범위 | `P4-R10`, `P9-R18`~`P9-R19`, `P10-R16`~`P10-R17` current-state contract 자동 보완 완료 | 실제 crop pixel·stale ADMIN·개발 API 수동 QA |
| 실제 개발 API | Series/FanTalk/Comments/file policy 수동 QA 대기 | ADMIN credential과 고정 fixture 필요 |
| 브라우저·인가 수동 QA | 실제 crop pixel 비교와 stale ADMIN server 확인 대기 | Chromium/mobile Chrome 환경과 stale ADMIN fixture 필요 |
| 문서 구조 | `P10-R9`에서 Goal 실행형 필수 section navigation 복구 | 기존 Progress·Decision 이력은 보존 |
@@ -75,7 +75,7 @@ ADMIN이 로그인한 뒤 AI 캐릭터를 선택하고, 선택한 캐릭터 문
## Progress
상세 진행 기록은 [검증 기록](#7-검증-기록)에 누적한다. `P9-R18`~`P9-R19`, `P10-R16`~`P10-R17`에서 finding·Task·H2·실제 마지막 Progress contract를 보완해 자동 보완 완료로 판정했다. 실제 crop pixel·stale ADMIN·Series/FanTalk/Comments/file policy 수동 QA는 별도 대기다.
상세 진행 기록은 [검증 기록](#7-검증-기록)에 누적한다. `P4-R10`은 가격 기본값 0, native number stepper, 조건부 유료 옵션과 preview duration offset을 자동 검증 완료했다. `P9-R18`~`P9-R19`, `P10-R16`~`P10-R17`에서 finding·Task·H2·실제 마지막 Progress contract를 보완해 자동 보완 완료로 판정했다. 실제 crop pixel·stale ADMIN·Series/FanTalk/Comments/file policy 수동 QA는 별도 대기다.
## Decision Log
@@ -83,7 +83,7 @@ ADMIN이 로그인한 뒤 AI 캐릭터를 선택하고, 선택한 캐릭터 문
## 발견된 문제
제품·Playwright 설정의 신규 문제는 없다. `P9-R18`, `P9-R19`, `P10-R16`, `P10-R17`로 finding·Task 경계, fenced·중복 H2, 최신 및 중복 Progress marker contract를 보완했다. 실제 crop pixel·stale ADMIN과 개발 API Series/FanTalk/Comments/file policy 수동 QA가 남아 있다.
제품·Playwright 설정의 신규 문제는 없다. `P4-R10`은 2026-08-04에 확정된 Audio form UX 정책을 현재 구현과 정렬했고, 가격 native stepper와 `0..99999` 검증을 Chromium/mobile Chrome mock E2E로 확인했다. `P9-R18`, `P9-R19`, `P10-R16`, `P10-R17`로 finding·Task 경계, fenced·중복 H2, 최신 및 중복 Progress marker contract를 보완했다. 실제 crop pixel·stale ADMIN과 개발 API Series/FanTalk/Comments/file policy 수동 QA가 남아 있다.
## 최종 보고 형식
@@ -2563,6 +2563,67 @@ field와 수정 금지 control이 실제 network 요청·화면에 일치하는
- **기능·시각 판정:** play/pause, seek, rate 1.5, volume 0.5, 단일 active track, 일반 오류·수동 retry와 API refetch delta 0을 확인했다. 독립 design-system/functional 및 visual/CJK reviewer가 같은 V3 세트를 각각 PASS로 판정했고 blocker는 없다.
- **Teardown:** Playwright browser는 종료됐다. QA-owned server는 없었고, QA 이전부터 실행 중인 8889의 PID 4837/4875는 사용자 프로세스로 보존했다.
### Task R4.10 — Audio form 입력 정책과 조건부 설정 UX 정렬
**Goal 실행 `P4-R10`:** 2026-08-04 확정 Audio form 정책을 구현하고 create/update payload 기본값과 접근 가능한 native control UX를 회귀로 고정한다.
- **시작 조건:** `P4-R9` 완료, PRD `AUDIO-008~010`, `AUDIO-019`, `AUDIO-033`과 2026-08-04 Decision Log 확인.
- **완료 증거:** tags, 숫자 전용 가격, 가격 조건부 설정·초기화, 숨긴 OpenAPI field의 고정 payload, 예약 datetime과 preview duration offset control, 선택 카드 문법이 RED/GREEN/REFACTOR test와 Chromium/mobile Chrome mock browser QA로 검증된다. 실행 결과와 QA teardown을 `§7 검증 기록`에 누적한다.
- **범위 밖:** OpenAPI/backend schema 변경, `limited` 의미 추정, `isOnlyRental` 동기화 규칙 발명, custom date/time picker, 새 form library, 기존 dirty `AudioContentForm.tsx`와 `audio-form.test.tsx`의 사용자 reorder 변경.
**Files:**
- Modify: `DESIGN.md`
- Modify: `src/features/audio-contents/components/AudioContentForm.tsx`
- Create: `src/features/audio-contents/components/AudioTagInput.tsx`
- Create: `src/features/audio-contents/components/AudioContentCreateOptions.tsx`
- Modify: `src/features/audio-contents/components/ReleaseScheduleField.tsx`
- Modify: `src/features/audio-contents/components/audio-content-form-helpers.ts`
- Modify: `src/features/audio-contents/tests/audio-form.test.tsx`
- Modify: `src/features/audio-contents/tests/audio-form-update.test.tsx`
- Modify: `src/features/audio-contents/tests/audio-form-create-red.test.tsx` by consolidating its create assertions into the focused form suite, then remove the redundant file only after those assertions pass there
- Modify: `tests/e2e/audio-content.spec.ts`
**Interfaces:**
- `AudioTagInput({ value, onChange })` consumes and emits a comma-separated `string`; it renders chips, commits nonempty trimmed entries on Enter or comma, and exposes a labeled remove button for each chip.
- `AudioContentCreateOptions({ price, value, onChange })` owns only create-only option controls. For `price > 0`, it renders native selection-card controls for `purchaseOption`, `isGeneratePreview`, `isPointAvailable`, `isAdult`, `isCommentAvailable`, and `isFullDetailVisible`. For price `0`, it hides purchase/preview/point controls and emits `purchaseOption: "BOTH"`, `isGeneratePreview: false`, `isPointAvailable: false`, `previewStartTime: null`, `previewEndTime: null`.
- Price form state accepts digits only and serializes an integer in `0..99999`. `0` means free. Create serialization always sends `limited: null`, `languageCode: null`, and `isOnlyRental: false`, without rendering controls for those fields.
- `ReleaseScheduleField({ releaseMode, value, onChange })` renders native radio selection cards and only renders `<input type="datetime-local">` in scheduled mode. It does not call `showPicker()`.
- Preview start/end controls render only when `isGeneratePreview` is true. They use text inputs for audio duration offsets in full `HH:MM:SS`; create serialization sends the entered `HH:mm:ss` value without shorthand normalization.
- `DESIGN.md` defines one selection-card visual grammar for native radio and checkbox controls, preserving visible labels, keyboard operation, focus indication, and 44px minimum targets.
**TDD 예외 사유:** 이 Task의 PRD·plan 갱신은 문서 산출물만 바꾸므로 application behavior를 대상으로 한 새 실패 test를 만들지 않는다. 문서 계약 test와 Markdown whitespace 검사가 해당 변경의 직접 검증이다. 아래 RED/GREEN/REFACTOR는 애플리케이션 구현 시작 후에만 실행한다.
**대체 검증 방법:** `npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts`와 scoped `git diff --check`로 문서 계약과 형식을 확인하고, 실제 application RED/GREEN/REFACTOR 결과는 실행 뒤 Progress에만 기록한다.
**TDD 절차:**
- [x] **RED: 실패 테스트 작성/실패 확인** — `audio-form.test.tsx`에 Enter/comma chip 추가, remove button, comma-separated payload, numeric price와 `0/99999` 허용·`100000` 차단을 추가한다. `audio-form-update.test.tsx`에 update payload가 create-only control을 다시 보내지 않는 경계를 추가한다. `audio-form-create-red.test.tsx`의 create assertion은 focused 회귀로 유지한다. `tests/e2e/audio-content.spec.ts`에 예약/즉시 전환과 가격 0 전환 시 옵션 숨김·기본값 초기화, ArrowDown/ArrowUp min 0 stepper를 추가한다. focused Vitest가 초기 가격 `""`/text input과 기존 payload assertion으로 실패하는지 확인했다.
- [x] **GREEN: 최소 구현/통과 확인** — `AudioTagInput`, `AudioContentCreateOptions`, helper와 existing form/schedule field에 필요한 최소 조합만 추가했다. 가격 기본값 0, native `type="number" min="0" step="1"`, price=0 reset, fixed `limited/languageCode/isOnlyRental` serialization, 예약 datetime과 preview duration offset input 조건부 렌더링, radio/checkbox selection card를 구현하고 focused command를 통과시켰다.
- [x] **REFACTOR: 정리/회귀 확인** — create-only option state를 `AudioContentCreateOptions`에 국한하고 form helper의 serialization 중복만 정리했다. `DESIGN.md`와 구현의 선택 카드 규칙을 대조한 뒤 focused form tests, Audio feature regression, typecheck, lint, build, mock E2E를 실행했다.
**검증 기준:**
- **실행 명령:** `npm run test:run -- src/features/audio-contents/tests/audio-form.test.tsx src/features/audio-contents/tests/audio-form-update.test.tsx src/features/audio-contents/tests/audio-form-create-red.test.tsx`; `npm run test:run -- src/features/audio-contents`; `npm run typecheck`; `npm run lint`; `npm run build`; `npm run e2e:mock -- tests/e2e/audio-content.spec.ts`; `npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts`; `git diff --check -- DESIGN.md docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md src/features/audio-contents tests/e2e/audio-content.spec.ts`.
- **기대 결과:** 실행한 모든 명령이 exit 0이다. tags payload는 comma-separated string이고, price는 native number input으로 `0..99999` integer를 보낸다. price=0에서 purchase/preview/point control과 preview time은 없고 지정 기본값만 전송된다. `limited=null`, `languageCode=null`, `isOnlyRental=false`가 유지되며 세 field의 UI와 임의 동기화는 없다. 예약은 native `datetime-local`, preview는 full `HH:MM:SS` text duration offset input으로 조건부 렌더링한다. preview request는 입력한 `HH:mm:ss` 값을 그대로 보내며 `showPicker()` 호출은 없다.
- **수동 확인:** `npm run dev:mock`으로 desktop Chromium과 mobile Chrome에서 Audio 생성 form을 연다. keyboard로 tags를 Enter와 comma로 추가하고 remove button으로 삭제한다. 가격을 유료값에서 0으로 바꿔 숨김과 reset을 확인하고 다시 유료값으로 바꾼다. 예약 공개와 preview 생성 on/off를 전환해 native datetime과 text duration offset input, selection-card focus, 200% zoom, axe critical·serious 0건을 확인한다. mock browser와 dev server를 종료하고 QA가 시작한 listener만 정리한다.
- [x] RED/GREEN/REFACTOR의 실제 명령·test 수·browser QA·teardown 결과를 새 Progress 기록에 누적한다.
**P4-R10 실행 전 Progress — 2026-08-04:**
- 문서 정책 결정과 실행 계약만 확정했다. 애플리케이션 source, `DESIGN.md`, form test, E2E는 아직 수정하거나 실행하지 않았으므로 RED/GREEN/REFACTOR 결과와 browser QA 통과를 기록하지 않는다.
- 문서 대체 검증은 이 문서 갱신 직후 `mock-preview-docs.test.ts`와 scoped `git diff --check`로 수행하며, 그 결과는 현재 문서 변경의 검증일 뿐 `P4-R10` 구현 완료 증거가 아니다.
**P4-R10 완료 Progress — 2026-08-04:**
- **RED/GREEN:** preview duration 회귀는 `type="text"` 기대에 기존 `type="time"`이 반환되어 RED를 확인했고, 최소 구현 후 focused `1/1`, create form `10/10`, Audio `66/66`, 전체 Vitest `81 files / 434 tests`, 문서 계약 `9/9`가 통과했다.
- **가격 스테퍼 보완:** 가격 기본값 0과 native `type="number" min="0" step="1"` 회귀는 focused test에서 기존 빈 문자열/text input으로 RED를 확인했다. 최소 구현 후 focused form `18/18`, Audio `65/65`, 전체 Vitest `81 files / 433 tests`, `typecheck`, `lint`, `build:prod`, mock E2E `11 passed / 1 intentional skip`가 통과했다. Chromium/mobile Chrome에서 ArrowDown at 0은 0 유지, ArrowUp은 1 증가를 확인했다.
- **정적 검증:** `npm run typecheck`, `npm run lint`, `npm run build:prod`, TypeScript no-excuse 검사, `git diff --check`가 exit 0이었다. production build에는 기존 500kB chunk warning만 남았다.
- **브라우저 검증:** `npm run e2e:mock -- tests/e2e/audio-content.spec.ts`는 Chromium/mobile Chrome에서 `11 passed / 1 intentional skip / 0 failed`였다. 실제 1280/768 DOM에서 preview start/end는 `type=text`, `step=null`, 값 `00:00:30`/`01:00:05`, `input[type=time]=0`이었고 예약 입력만 `datetime-local`이었다. 375px은 기존 정책대로 mutation form을 숨겼으며 세 viewport 모두 horizontal overflow가 없었다.
- **시각·리뷰 판정:** `.playwright-mcp/audio-preview-duration-{1280,768,375-blocked}-20260804.png` fresh 캡처를 대상으로 design-system/functional 및 visual/CJK Oracle이 모두 blocker 없이 PASS했고, 최종 code reviewer도 APPROVE했다.
- **Teardown:** Playwright browser를 닫고 QA가 시작한 Vite PID `69030`과 parent PID `69001`을 종료했다. 이후 `127.0.0.1:8889` listener가 없음을 확인했다.
---
## Phase 5. Series vertical slice

View File

@@ -6,7 +6,7 @@
|---|---|
| 문서 상태 | OpenAPI 반영 구현 기준 |
| 작성일 | 2026-07-25 |
| 최종 수정일 | 2026-07-30 |
| 최종 수정일 | 2026-08-04 |
| 대상 제품 | AI 캐릭터 전용 독립 관리자 웹 |
| 구현 대상 | React + TypeScript + Vite SPA |
| UI 기반 | Tailwind CSS + shadcn/ui |
@@ -223,9 +223,9 @@ AI 캐릭터를 생성하고, AI 캐릭터가 사람 크리에이터처럼 콘
| AUDIO-005 | 제외 | 현 OpenAPI에 status filter가 없으므로 status query를 보내거나 현재 page를 client에서 status별로 거르지 않는다. |
| AUDIO-006 | 확정 | 생성 요청에는 `isActive`를 보내지 않는다. |
| AUDIO-007 | 확정 | 일반 수정 요청에서는 `isActive`를 생략하고 soft delete 요청에만 `isActive=false`를 보낸다. `isActive=true`는 전송하지 않는다. |
| AUDIO-008 | 확정 | 공개 방식은 “지금 즉시 공개”와 “예약 공개” 선택 버튼으로 제공한다. |
| AUDIO-009 | 확정 | 생성 시 즉시 공개가 기본값이며 `releaseDate=null`을 보내고 `timezone`은 보내지 않는다. 예약 날짜 입력은 비활성화하고 기존 값을 지운다. |
| AUDIO-010 | 확정 | 예약 공개를 선택한 경우에만 날짜·시간을 입력할 수 있고 미래 시각 필수다. |
| AUDIO-008 | 확정 | 공개 방식은 native radio를 사용하는 “지금 즉시 공개”와 “예약 공개” 선택 카드로 제공한다. radio와 checkbox는 같은 선택 카드 시각 문법을 사용한다. |
| AUDIO-009 | 확정 | 생성 시 즉시 공개가 기본값이며 `releaseDate=null`을 보내고 `timezone`은 보내지 않는다. 즉시 공개에서는 예약 일시 입력을 렌더링하지 않고 예약 값을 지운다. |
| AUDIO-010 | 확정 | 예약 공개를 선택한 경우에만 native `datetime-local` 입력을 렌더링하고 미래 시각 필수로 받는다. `showPicker()` 같은 custom picker 호출은 사용하지 않는다. |
| AUDIO-011 | 확정 | 예약 시각은 Asia/Seoul로 입력·표시하되, 생성 API에는 해당 시각을 클라이언트에서 UTC로 변환한 ISO-8601 `Z` 형식의 `releaseDate`를 보낸다. 예를 들어 `2026-07-29 18:00` Asia/Seoul은 `2026-07-29T09:00:00Z`로 전송한다. |
| AUDIO-012 | 확정 | 생성 multipart의 `contentFile`, `coverImage`, `request`는 필수다. 수정은 `coverImage``request`만 허용하므로 오디오 원본 파일 교체 UI를 제공하지 않는다. |
| AUDIO-013 | 확정 | 오디오 확장자는 `.mp3`, `.aac`, `.m4a`를 허용한다. WAV는 허용하지 않는다. |
@@ -234,7 +234,7 @@ AI 캐릭터를 생성하고, AI 캐릭터가 사람 크리에이터처럼 콘
| AUDIO-016 | 확정 | 확장자와 MIME만 신뢰하지 않고 실제 컨테이너·코덱 검증은 백엔드가 수행해야 한다. |
| AUDIO-017 | 확정 | 업로드 진행률, 취소, 전체 재시도를 제공하고 resumable upload는 제공하지 않는다. |
| AUDIO-018 | 확정 | 업로드 실패 후 입력한 폼 값과 선택 가능한 파일 상태를 최대한 유지한다. |
| AUDIO-019 | 확정 | 가격 단위는 “캔”이고 `0..99999` 정수다. 0은 무료이며 UI는 예: `1,000캔`으로 표시한다. |
| AUDIO-019 | 확정 | 가격 단위는 “캔”이고 입력·저장 값은 숫자만 사용한다. 생성 기본값은 0이며 native number input은 `min=0`, `step=1`로 방향키 위/아래 증감을 제공한다. 유효 범위는 `0..99999` 정수이며 0은 무료다. payload에는 정수 price를 보낸다. |
| AUDIO-020 | 확정 | Audio 생성·수정 request에는 `seriesIds`가 없다. 시리즈 연결은 Audio form이 아니라 Series 콘텐츠 연결 endpoint와 Phase 5 UI에서 관리한다. |
| AUDIO-021 | 확정 | 목록과 상세에서 오디오를 재생할 수 있다. |
| AUDIO-022 | 확정 | 오디오 목록 API는 `isActive=true`인 항목만 반환한다. request에는 활성 상태 query를 추가하지 않고 응답에도 client-side 활성 filter를 적용하지 않는다. status query는 여전히 제공하지 않는다. |
@@ -248,7 +248,7 @@ AI 캐릭터를 생성하고, AI 캐릭터가 사람 크리에이터처럼 콘
| AUDIO-030 | 확정 | 상세 GET에는 `timezone` query를 보내지 않는다. 응답의 nullable `releaseDate`는 ISO-8601 UTC `Z` 값으로 소비하고 화면 표시 시 Asia/Seoul로 변환한다. |
| AUDIO-031 | 확정 | 생성 성공은 `data.contentId`를 사용해 상세로 이동할 수 있다. 수정·soft delete 성공은 `data=null`이므로 기존 ID 기준 cache를 무효화한다. |
| AUDIO-032 | 확정 | 생성 `request`는 필수 `title`, `detail`, `tags`, `price`와 OpenAPI의 optional field만 보낸다. 수정은 `title`, `detail`, `tags`, `price`, `isAdult`, `isActive`, `isPointAvailable`, `isCommentAvailable`만 변경할 수 있다. |
| AUDIO-033 | 확정 | 생성 form은 OpenAPI의 `purchaseOption`, `limited`, `isAdult`, `isGeneratePreview`, `isOnlyRental`, `isPointAvailable`, `isCommentAvailable`, `isFullDetailVisible`, `previewStartTime`, `previewEndTime`, `languageCode`를 계약 enum·type과 default에 맞춰 제공한다. 계약에 없는 추가 상관관계 validation은 만들지 않는다. |
| AUDIO-033 | 확정 | 태그는 chip으로 표시하며 Enter 또는 comma로 추가하고 각 chip의 remove button으로 삭제한다. request `tags` payload는 comma-separated string을 유지한다. 가격이 0보다 클 때만 `purchaseOption`, preview 생성 여부·시간, point 사용 가능 여부를 표시한다. 가격을 0으로 바꾸면 `purchaseOption=BOTH`, `isGeneratePreview=false`, `isPointAvailable=false`, `previewStartTime=null`, `previewEndTime=null`으로 즉시 초기화한다. preview 시작·종료는 시각이 아니라 오디오 duration 내 offset이며 preview 생성이 켜진 경우에만 text control로 완전한 `HH:MM:SS`를 입력한다. request는 입력한 `HH:mm:ss` 값을 그대로 보낸다. 문서화되지 않은 nullable `limited` UI, `languageCode` UI, 독립 `isOnlyRental` UI는 제공하지 않으며 각각 `limited=null`, `languageCode=null`, `isOnlyRental=false`를 계속 전송한다. `isAdult`, `isCommentAvailable`, `isFullDetailVisible`과 preview 생성 여부는 native checkbox 선택 카드로 제공한다. |
| AUDIO-034 | 확정 | 공통 관리자 오디오 플레이어는 오디오 콘텐츠 목록·상세와 커뮤니티 목록·Sheet에 동일한 compact audio-only control bar를 사용한다. Plyr audio player를 1차 시각 기준, Media Chrome audio player를 control anatomy 기준으로 삼아 표준 viewport에서는 재생·진행·현재/전체 시간·배속·음량을 상시 텍스트 label 없이 한 줄에 표시한다. 별도 image·video 영역을 만들지 않고 기존 화면의 cover·게시물 media는 그대로 유지한다. |
현 수정 계약에는 `releaseDate`, `themeId`, `contentFile`이 없다. 따라서 공개 예약·테마·오디오 원본 변경은 생성 화면에서만 제공하고 수정 화면에서는 읽기 전용으로 표시한다.
@@ -936,3 +936,4 @@ vertical slice와 남은 수동 QA에서 검증한다. `EXT-006`은 현재 확
| 2026-07-29 | 현재 FanTalk UI는 목록 item 기반 Sheet와 backend 반환 순서만 사용하므로 별도 상세 GET·답변 상태 filter·sort query가 필요하지 않다고 확정했다. 중복 생성 전용 오류 key 분기도 제외하고 일반 오류 후 목록을 재조회한다. 답변 1개 불변식은 `FANTALK-003`의 backend 수용 기준으로 server integration에서 검증하므로 `EXT-004`를 해결로 종결한다. |
| 2026-07-31 | 사용자 직접 지시에 따라 로컬 자동 Gate와 지원 browser 범위는 데스크톱 Chrome·모바일 Chrome의 Chrome 2종으로 한정한다. Playwright는 Chromium/mobile Chrome project만 유지하고, 현재 제품 지원 대상이 아닌 WebKit·Mobile Safari 자동 실행과 수동 QA는 테스트 시간을 크게 늘리므로 현재 릴리스 범위에서 제외한다. |
| 2026-08-03 | 공통 `AdminAudioPlayer`의 native media 동작과 단일 재생·오류 계약은 유지하고, Plyr·Media Chrome의 audio-only control 배치를 참고한 compact 가로형 UI로 개선한다. 플레이어 내부 image·video 영역과 새 외부 라이브러리·waveform은 추가하지 않으며 오디오 콘텐츠 목록·상세와 커뮤니티 목록·Sheet에 동일하게 적용한다. |
| 2026-08-04 | Audio form은 tags를 chip으로 입력하고 Enter/comma 추가와 remove button 삭제를 제공하되 payload는 comma-separated string으로 유지한다. 가격은 숫자만 표시·저장하는 `0..99999` 정수이며 0은 무료다. 가격이 0보다 클 때만 purchase option, preview 생성·시간, point 사용 가능 여부를 보이고, 0 전환 시 `purchaseOption=BOTH`, `isGeneratePreview=false`, `isPointAvailable=false`, preview times=`null`로 초기화한다. `limited`는 문서화되지 않은 NullableInt32이므로 UI를 제거하고 `null`을 보내며, `languageCode` UI는 제거하고 `null`, 독립 `isOnlyRental` UI는 제거하고 `false`를 보낸다. preview 시작·종료는 오디오 duration offset으로 text 입력하며 완전한 `HH:MM:SS`만 안내하고 request에는 입력한 `HH:mm:ss` 값을 그대로 보낸다. 예약 일시는 예약 공개에서만 native `datetime-local`로 렌더링하고 custom `showPicker()`는 사용하지 않는다. native radio/checkbox는 하나의 선택 카드 시각 문법을 사용한다. |