768 lines
61 KiB
Markdown
768 lines
61 KiB
Markdown
# AI 캐릭터 관리자 웹 PRD
|
||
|
||
## 문서 정보
|
||
|
||
| 항목 | 내용 |
|
||
|---|---|
|
||
| 문서 상태 | 인터뷰 반영 초안 |
|
||
| 작성일 | 2026-07-25 |
|
||
| 대상 제품 | AI 캐릭터 전용 독립 관리자 웹 |
|
||
| 구현 대상 | React + TypeScript + Vite SPA |
|
||
| UI 기반 | Tailwind CSS + shadcn/ui |
|
||
| 관련 계획 | [plan-task.md](./plan-task.md) |
|
||
| 정규화 계약 | [api-contract.md](./api-contract.md) |
|
||
|
||
### 상태 표기
|
||
|
||
- **확정**: 이번 인터뷰에서 합의되어 구현 기준으로 사용할 사항
|
||
- **미결**: 프론트엔드 제품·UI 또는 운영 정책이 결정되지 않아 후속 인터뷰나 UI 검토가 필요한 사항
|
||
- **외부 의존**: 프론트엔드가 결정할 사항이 아니며, 해당 기능의 network integration 전에 백엔드가 제공해야 하는 계약
|
||
- **권고**: 미결 사항에 대한 현재 추천안이며, 확정 전에는 계약으로 간주하지 않음
|
||
|
||
### 문서 유지보수 원칙
|
||
|
||
1. 요구사항이 변경되면 `prd.md`의 결정 기록, 기능 요구사항, API 계약 보정표, 미결 사항을 함께 갱신한다.
|
||
2. 구현 범위나 순서가 바뀌면 같은 디렉터리의 `plan-task.md`도 같은 변경에서 갱신한다.
|
||
3. 사용자 인터뷰 결정과 최초 API Contract가 충돌하면 이 문서의 “API 계약 보정사항”을 우선한다.
|
||
4. 미결 사항과 외부 의존 계약은 추측으로 구현하지 않는다. **미결**에는 추천안을, **외부 의존**에는 제공 주체와 영향을 함께 기록한다.
|
||
5. 완료된 미결 사항과 제공 완료된 외부 의존 계약은 결정일과 결정 내용을 “결정 기록”에 추가한 뒤 관련 수용 기준까지 갱신한다.
|
||
|
||
---
|
||
|
||
## 1. Overview
|
||
|
||
AI 캐릭터를 생성하고, AI 캐릭터가 사람 크리에이터처럼 콘텐츠·시리즈·커뮤니티·FanTalk·댓글 활동을 수행하도록 관리하는 독립 관리자 웹을 만든다.
|
||
|
||
관리자는 ADMIN 권한으로 로그인한 뒤 AI 캐릭터를 선택한다. 이후의 모든 생성·수정·비활성화 작업은 선택한 `characterId`와 연결된 AI 캐릭터 크리에이터의 활동으로 저장된다. 관리자가 AI 캐릭터 계정으로 직접 로그인하거나 토큰을 교환하는 방식은 사용하지 않는다.
|
||
|
||
이번 문서는 요구사항과 구현 계획만 정의한다. 애플리케이션 코드는 이번 단계에서 수정하지 않는다.
|
||
|
||
## 2. Problem Statement
|
||
|
||
현재 AI 캐릭터는 연결된 `creator(memberKind = AI_CHARACTER)`를 가지지만, 운영자가 캐릭터의 전체 활동을 한곳에서 관리할 독립 UI가 없다.
|
||
|
||
운영자는 다음 문제를 해결해야 한다.
|
||
|
||
- 캐릭터와 연결된 creator의 관계를 이해하지 않아도 안전하게 캐릭터를 관리해야 한다.
|
||
- 여러 캐릭터의 리소스가 섞이지 않도록 선택한 캐릭터 문맥 안에서만 작업해야 한다.
|
||
- 오디오 콘텐츠를 관리자 화면에서 즉시 재생해 검수해야 한다.
|
||
- 예약 공개, 시리즈 연결과 순서, 게시글 고정, FanTalk 단일 답변 같은 도메인 규칙을 UI에서 명확히 안내해야 한다.
|
||
- 데스크톱에서는 전체 운영을 수행하고 모바일에서는 조회와 긴급 응대가 가능해야 한다.
|
||
- 최초 API Contract의 잘못되었거나 누락된 필드를 구현 전에 바로잡아야 한다.
|
||
|
||
## 3. Goals
|
||
|
||
### 3.1 제품 목표
|
||
|
||
- AI 캐릭터 목록 검색, 상세 조회, 생성, 수정, 비활성화를 제공한다.
|
||
- 선택한 캐릭터 문맥에서 오디오 콘텐츠, 시리즈, 커뮤니티 게시글을 관리한다.
|
||
- 선택한 캐릭터로 FanTalk에 한 번 답변하고 기존 답변을 수정할 수 있게 한다.
|
||
- 오디오 콘텐츠와 커뮤니티 첨부 오디오를 관리자 화면에서 재생할 수 있게 한다.
|
||
- 오디오 콘텐츠와 커뮤니티 게시글의 댓글·답글을 캐릭터 명의로 관리한다.
|
||
- 비활성 리소스와 권한 오류를 안전하게 처리하고 의도하지 않은 변경을 방지한다.
|
||
|
||
### 3.2 UX 목표
|
||
|
||
- 캐릭터 선택 이후 모든 화면에서 현재 대상 캐릭터를 분명히 표시한다.
|
||
- 데이터가 많은 운영 화면을 조밀하지만 빠르게 탐색할 수 있게 한다.
|
||
- 업로드, 저장, 비활성화, 연결 해제 등 비동기 작업의 상태와 결과를 즉시 피드백한다.
|
||
- 데스크톱·태블릿에서는 전체 기능을, 모바일에서는 합의된 조회·응대 기능을 제공한다.
|
||
- 기본적인 키보드 탐색, 포커스 표시, 입력 레이블, 오류 연결을 보장한다.
|
||
|
||
## 4. Non-Goals
|
||
|
||
- 일반 사용자 또는 사람 크리에이터를 관리하는 기능
|
||
- 관리자 계정을 AI 캐릭터 계정으로 전환하거나 AI 캐릭터 JWT를 발급하는 기능
|
||
- refresh token 또는 자동 access token 갱신
|
||
- 비활성 리소스 복원
|
||
- hard delete
|
||
- FanTalk 답변 삭제 또는 두 번째 답변 추가
|
||
- WAV 업로드
|
||
- 오디오 최대 재생 길이 제한
|
||
- 모바일에서 캐릭터·오디오·시리즈·커뮤니티 리소스 생성/수정/비활성화, 파일 업로드, 시리즈 연결/순서 변경
|
||
- 다국어 UI
|
||
- 초기 릴리스의 다크 모드, 테마 전환 버튼과 시스템 색상 테마 연동
|
||
- 정식 WCAG 2.2 AA 인증 또는 외부 접근성 감사
|
||
- 백엔드가 담당할 creator 생성·프로필 동기화의 조건부 정책 변경
|
||
- 비활성 ID에 대한 상세 GET 반환 여부와 오류 status 등 백엔드 조회 정책 결정
|
||
|
||
## 5. Target Users
|
||
|
||
### 5.1 주 사용자
|
||
|
||
- AI 캐릭터와 해당 캐릭터의 콘텐츠를 운영하는 ADMIN
|
||
- 콘텐츠 공개 상태와 오디오 품질을 확인하는 운영 담당자
|
||
- 모바일에서 댓글 또는 FanTalk에 긴급 응대하는 운영 담당자
|
||
|
||
### 5.2 권한
|
||
|
||
- JWT claim의 role과 현재 DB role이 모두 ADMIN이어야 한다.
|
||
- 비ADMIN JWT는 403으로 차단한다.
|
||
- JWT claim은 ADMIN이지만 현재 DB role이 비ADMIN인 stale claim도 403으로 차단한다.
|
||
- 관리자는 선택한 캐릭터의 creator 권한으로 리소스를 작성하지만 인증 주체는 계속 ADMIN이다.
|
||
|
||
## 6. 핵심 사용자 흐름
|
||
|
||
1. 관리자가 이메일과 비밀번호로 로그인한다.
|
||
2. AI 캐릭터 목록에서 이름으로 검색하고 캐릭터를 선택한다.
|
||
3. 캐릭터 워크스페이스의 기본 정보, 오디오, 시리즈, 커뮤니티, FanTalk 탭을 이동한다.
|
||
4. 활성 캐릭터라면 데스크톱·태블릿에서 리소스를 생성·수정·비활성화한다.
|
||
5. 오디오 플레이어로 캐릭터가 올린 오디오를 검수한다.
|
||
6. FanTalk에는 한 번만 답변하고 필요하면 기존 답변을 수정한다.
|
||
7. 오디오 또는 커뮤니티 댓글에 캐릭터 명의로 댓글·답글을 작성하거나 운영 정책에 따라 삭제한다.
|
||
8. 모바일에서는 리소스를 조회하고 오디오를 재생하며 댓글과 FanTalk 답변을 관리한다.
|
||
9. JWT가 만료되거나 폐기되면 인증 정보를 지우고 로그인 화면으로 이동한다.
|
||
|
||
## 7. 정보 구조와 라우팅
|
||
|
||
### 7.1 화면 구조
|
||
|
||
```text
|
||
/login
|
||
/ai-characters
|
||
/ai-characters/new
|
||
/ai-characters/:characterId
|
||
/profile
|
||
/audio-contents
|
||
/audio-contents/new
|
||
/audio-contents/:contentId
|
||
/audio-contents/:contentId/edit
|
||
/series
|
||
/series/new
|
||
/series/:seriesId
|
||
/series/:seriesId/edit
|
||
/series/orders
|
||
/community-posts
|
||
/community-posts/new
|
||
/fan-talks
|
||
/fan-talks/:fanTalkId
|
||
```
|
||
|
||
라우트 문자열은 구현 시 확정하되 다음 원칙은 고정한다.
|
||
|
||
- 캐릭터를 선택하지 않은 전역 화면은 로그인과 캐릭터 목록·생성뿐이다.
|
||
- 캐릭터 리소스 화면은 모두 URL에 `characterId`를 포함한다.
|
||
- 목록의 `search`, `status`, 답변 상태, `page`, `size`는 가능한 범위에서 URL query에 보존한다.
|
||
- 상세 GET이 제공되는 주요 리소스의 목록과 상세 화면은 새로고침과 직접 링크 진입이 가능해야 한다.
|
||
- 커뮤니티 게시글은 별도 상세·수정 route 없이 목록 행/카드에서 여는 Sheet를 사용한다. 페이지 새로고침은 목록을 다시 조회한다.
|
||
- 존재하지 않거나 다른 캐릭터 소유인 하위 리소스는 서버 결과에 따라 오류 화면으로 처리한다.
|
||
|
||
### 7.2 캐릭터 워크스페이스
|
||
|
||
- 상단에 캐릭터 이미지, 이름, 활성 상태, `characterId`를 항상 표시한다.
|
||
- 1차 탭은 기본 정보, 오디오, 시리즈, 커뮤니티, FanTalk로 구성한다.
|
||
- 워크스페이스 진입·복원용 상세 성공 응답으로 `isActive=false`인 캐릭터 데이터를 받은 경우에는 읽기 전용 배너를 표시하고 모든 변경 진입점을 숨기거나 비활성화한다. soft delete mutation 성공 직후에는 이 규칙보다 `CHAR-014`의 목록 이동을 우선한다. 비활성 ID의 상세 GET 반환 여부는 프론트엔드가 규정하지 않는다.
|
||
- 브레드크럼으로 캐릭터 목록과 현재 리소스 위치를 표시한다.
|
||
|
||
## 8. 기능 요구사항
|
||
|
||
### 8.1 인증과 세션
|
||
|
||
| ID | 상태 | 요구사항 |
|
||
|---|---|---|
|
||
| AUTH-001 | 확정 | 독립 관리자 웹에 자체 로그인 화면을 제공한다. |
|
||
| AUTH-002 | 확정 | 로그인 입력은 이메일과 비밀번호다. |
|
||
| AUTH-003 | 확정 | ADMIN만 보호 라우트에 접근할 수 있다. |
|
||
| AUTH-004 | 확정 | refresh token과 자동 갱신을 사용하지 않는다. |
|
||
| AUTH-005 | 확정 | 401 수신 시 보관 중인 인증 상태를 제거하고 로그인으로 이동한다. |
|
||
| AUTH-006 | 확정 | 403 수신 시 접근 거부 화면을 표시하며 권한이 필요한 작업을 실행하지 않는다. |
|
||
| AUTH-007 | 확정 | 모든 API 요청에 `Accept-Language: ko`를 보낸다. |
|
||
| AUTH-008 | 확정 | 로그인은 `POST /admin/member/login`에 email/password JSON body를 보낸다. |
|
||
| AUTH-009 | 확정 | 로그인 성공 시 응답 `data.token`과 `data.role`을 받고 role은 `ADMIN`이어야 한다. |
|
||
| AUTH-010 | 확정 | 보호 API와 로그아웃에는 `Authorization: Bearer {jwt-token}` header를 사용한다. |
|
||
| AUTH-011 | 확정 | 관리자 전용 로그아웃 endpoint는 없으며 공통 `POST /member/logout`을 body 없이 호출한다. |
|
||
| AUTH-012 | 확정 | 로그인 성공 시 JWT와 ADMIN role을 `sessionStorage`에만 저장한다. 같은 탭의 새로고침에서는 session을 복원하고 탭 종료 시 브라우저 동작에 따라 제거한다. `localStorage`, IndexedDB, cookie에는 저장하지 않는다. |
|
||
| AUTH-013 | 확정 | `POST /member/logout`이 성공하거나 네트워크·비2xx 오류로 실패해도 프론트엔드는 `sessionStorage` 인증 정보를 제거하고 로그인 화면으로 이동한다. 실패 시 서버 로그아웃 확인 실패 경고를 표시하며 session을 복원하지 않는다. |
|
||
|
||
### 8.2 AI 캐릭터
|
||
|
||
| ID | 상태 | 요구사항 |
|
||
|---|---|---|
|
||
| CHAR-001 | 확정 | 이름 검색, 페이지네이션이 있는 캐릭터 목록을 제공한다. |
|
||
| CHAR-002 | 확정 | 캐릭터 상세, 생성, 수정, 비활성화를 제공한다. |
|
||
| CHAR-003 | 확정 | 생성 입력은 `name`, `description`, 선택 이미지, 선택 `originalWorkId`다. |
|
||
| CHAR-004 | 확정 | 생성 요청에는 `isActive`를 보내지 않는다. 최초 활성 상태는 백엔드가 결정한다. |
|
||
| CHAR-005 | 확정 | `externalCharacterId`는 존재하지 않는 값이므로 모든 요청·응답·UI에서 제거한다. |
|
||
| CHAR-006 | 확정 | 일반 수정 요청에서는 `isActive`를 생략하고 soft delete 요청에만 `isActive=false`를 보낸다. `isActive=true`는 전송하지 않는다. |
|
||
| CHAR-007 | 확정 | 복원·hard delete는 제공하지 않는다. 워크스페이스 진입·복원용 상세 성공 응답으로 `isActive=false`인 캐릭터를 받은 경우 해당 workspace는 read-only로 처리한다. soft delete mutation 성공 직후에는 `CHAR-014`를 우선하며, 비활성 ID 상세 조회 정책은 백엔드 범위다. |
|
||
| CHAR-008 | 확정 | 캐릭터 생성 시 연결된 `creator(memberKind = AI_CHARACTER)` 생성은 백엔드가 함께 수행한다. |
|
||
| CHAR-009 | 확정 | 캐릭터 이름·설명·이미지 변경 시 creator의 nickname·introduce·profile image 동기화는 현재 백엔드가 수행한다. |
|
||
| CHAR-010 | 확정 | creator 상황에 따른 조건부 생성·동기화는 다음 백엔드 범위이며 현재 UI 범위가 아니다. |
|
||
| CHAR-011 | 확정 | `creatorMemberId`, `creatorNickname` 등 응답으로 제공되는 creator 정보는 읽기 전용으로 표시한다. |
|
||
| CHAR-012 | 확정 | 캐릭터 목록은 활성 캐릭터만 반환하며 활성 상태 filter/query를 제공하지 않는다. |
|
||
| CHAR-013 | 외부 의존 | 원작 검색 선택기에 필요한 lookup API와 원작 미선택 직렬화 계약은 백엔드가 제공해야 한다. 제공 전에는 원작 선택 network integration을 구현하지 않는다. |
|
||
| CHAR-014 | 확정 | 캐릭터 soft delete 성공 시 캐릭터 active-only 목록으로 이동하고 성공 알림을 표시한다. 현재 상세 화면에 머물지 않는다. |
|
||
|
||
#### 캐릭터 생성·수정 폼
|
||
|
||
- 이름과 설명은 명시적인 레이블과 필드 오류 영역을 가진다.
|
||
- 이미지는 미리보기, 교체, 새로 선택한 파일의 선택 취소를 제공한다. 기존 이미지 자체를 제거하는 기능은 contract가 없어 현재 범위가 아니다.
|
||
- 원작은 이름 검색형 Combobox로 선택한다. 미선택은 허용하되 multipart JSON에서 `null`을 보낼지 key를 생략할지는 API 계약으로 확정한다.
|
||
- 비활성화는 폼 Switch가 아니라 영향 범위를 설명하는 확인 Dialog로 실행한다.
|
||
- 저장 중 중복 제출을 막고 성공 후 상세 데이터를 다시 동기화한다.
|
||
|
||
### 8.3 오디오 콘텐츠
|
||
|
||
| ID | 상태 | 요구사항 |
|
||
|---|---|---|
|
||
| AUDIO-001 | 확정 | 선택한 캐릭터의 오디오 목록·검색·상태 필터·상세·생성·수정·비활성화를 제공한다. |
|
||
| AUDIO-002 | 확정 | 상태 값은 `OPEN`과 `SCHEDULED` 두 개뿐이다. |
|
||
| AUDIO-003 | 확정 | `OPEN`은 현재 출시된 콘텐츠, `SCHEDULED`는 미래 출시 예약 콘텐츠다. |
|
||
| AUDIO-004 | 확정 | 상태는 백엔드가 공개 시각을 기준으로 계산해 반환하고 프론트엔드는 재계산하지 않는다. |
|
||
| AUDIO-005 | 확정 | 상태 필터를 보내지 않은 경우의 결과 집합도 백엔드가 결정해 반환한다. |
|
||
| AUDIO-006 | 확정 | 생성 요청에는 `isActive`를 보내지 않는다. |
|
||
| AUDIO-007 | 확정 | 일반 수정 요청에서는 `isActive`를 생략하고 soft delete 요청에만 `isActive=false`를 보낸다. `isActive=true`는 전송하지 않는다. |
|
||
| AUDIO-008 | 확정 | 공개 방식은 “지금 즉시 공개”와 “예약 공개” 두 선택 버튼으로 제공한다. |
|
||
| AUDIO-009 | 확정 | 생성 시 즉시 공개가 기본값이며 `releaseDateUtc=null`을 보낸다. 예약 날짜 입력은 비활성화하고 기존 값을 지운다. |
|
||
| AUDIO-010 | 확정 | 예약 공개를 선택한 경우에만 날짜·시간을 입력할 수 있고 미래 시각이 필수다. |
|
||
| AUDIO-011 | 확정 | 예약 시각은 Asia/Seoul로 입력·표시하고 API에는 UTC ISO-8601 `Z` 값으로 변환해 보낸다. |
|
||
| AUDIO-012 | 확정 | 생성 시 cover image와 audio file은 필수이며 수정 시 교체 파일은 선택이다. |
|
||
| AUDIO-013 | 확정 | 오디오 확장자는 `.mp3`, `.aac`, `.m4a`를 허용한다. WAV는 허용하지 않는다. |
|
||
| AUDIO-014 | 확정 | canonical MIME은 `audio/mpeg`, `audio/aac`, `audio/mp4`다. |
|
||
| AUDIO-015 | 확정 | 오디오 파일의 운영 기준 최대 크기는 1,024MB이고 최대 재생 길이는 제한하지 않는다. |
|
||
| AUDIO-016 | 확정 | 확장자와 MIME만 신뢰하지 않고 실제 컨테이너·코덱 검증은 백엔드가 수행해야 한다. |
|
||
| AUDIO-017 | 확정 | 업로드 진행률, 취소, 전체 재시도를 제공하고 resumable upload는 제공하지 않는다. |
|
||
| AUDIO-018 | 확정 | 업로드 실패 후 입력한 폼 값과 선택 가능한 파일 상태를 최대한 유지한다. |
|
||
| AUDIO-019 | 확정 | 가격 단위는 “캔”이고 0 이상의 정수다. 0은 무료이며 UI는 예: `1,000캔`으로 표시한다. |
|
||
| AUDIO-020 | 확정 | 오디오를 여러 시리즈에 연결할 수 있도록 `seriesIds` 다중 선택을 제공한다. |
|
||
| AUDIO-021 | 확정 | 목록과 상세에서 오디오를 재생할 수 있다. |
|
||
| AUDIO-022 | 확정 | 오디오 목록은 활성 오디오만 반환하며 활성 상태 filter/query를 제공하지 않는다. 기존 `status=OPEN|SCHEDULED` 공개 상태 필터는 유지한다. |
|
||
| AUDIO-023 | 확정 | 최대 크기는 decimal 1,024MB인 `1,024,000,000 bytes` 이하이며 `1,024,000,001 bytes`부터 거부한다. 오디오 콘텐츠와 커뮤니티 첨부 audio에 동일하게 적용한다. |
|
||
| AUDIO-024 | 확정 | `audio/x-m4a`는 `.m4a` 파일에 한해 호환 MIME으로 허용한다. 실제 MP4/M4A 컨테이너·코덱 검증을 통과해야 하며 다른 확장자와의 조합은 거부한다. |
|
||
| AUDIO-025 | 확정 | 오디오 soft delete 성공 시 선택 캐릭터의 오디오 active-only 목록으로 이동하고 성공 알림을 표시한다. 현재 상세 화면에 머물지 않는다. |
|
||
| AUDIO-026 | 확정 | 재생 오류를 signed URL 만료로 구분하거나 추정하지 않는다. media error만으로 상세·목록을 자동 재조회하거나 자동 재생하지 않고, 일반 오류와 수동 재시도·페이지 새로고침 안내를 제공한다. |
|
||
|
||
수정 화면은 즉시 공개로 재초기화하지 않는다. 서버의 기존 `releaseDateUtc`와 `status`로 공개 방식과 날짜를 초기화하고, 관리자가 바꾸지 않으면 기존 값을 유지한다.
|
||
|
||
#### 관리자 오디오 플레이어
|
||
|
||
- 재생/일시정지, 탐색, 현재/전체 시간, 볼륨, 배속을 제공한다.
|
||
- 명시적인 다운로드 버튼은 제공하지 않는다.
|
||
- 여러 행의 플레이어가 동시에 재생되지 않게 현재 재생 항목을 단일화한다.
|
||
- 재생 오류는 원인을 signed URL 만료로 구분하지 않고 “오디오를 재생할 수 없습니다”와 수동 재시도·페이지 새로고침 안내를 표시한다.
|
||
- media error 자체로 상세·목록 API를 자동 재조회하거나 `play()`를 자동 재호출하지 않는다.
|
||
- signed URL은 로그, 분석 이벤트, 영구 저장소에 기록하지 않는다.
|
||
- 커뮤니티 첨부 오디오는 URL 갱신만을 목적으로 요청하지 않는다. 사용자가 페이지를 새로고침하거나 mutation 후 cache 무효화 등 일반 목록 lifecycle로 재조회된 경우 새 응답의 URL을 사용한다. 별도 상세 조회나 URL 재발급 호출을 추가하지 않는다.
|
||
|
||
### 8.4 시리즈
|
||
|
||
| ID | 상태 | 요구사항 |
|
||
|---|---|---|
|
||
| SERIES-001 | 확정 | 목록·상세·생성·수정·비활성화, 콘텐츠 연결·해제, 시리즈 순서 변경을 제공한다. |
|
||
| SERIES-002 | 확정 | 상태 enum은 `PROCEEDING`(연재중), `SUSPEND`(휴재중), `COMPLETE`(완결)이다. `OPEN`은 유효하지 않다. |
|
||
| SERIES-003 | 확정 | 생성 시 state를 선택하거나 보내지 않는다. 초기 state는 백엔드가 결정하며 프론트엔드는 정확한 기본값을 알 필요 없이 생성 응답의 state를 그대로 표시한다. |
|
||
| SERIES-004 | 확정 | 수정 시 state를 선택할 수 있으며 선택하지 않으면 필드를 생략해 이전 상태를 유지한다. |
|
||
| SERIES-005 | 확정 | 연재 요일 enum은 `SUN`, `MON`, `TUE`, `WED`, `THU`, `FRI`, `SAT`, `RANDOM`이다. |
|
||
| SERIES-006 | 확정 | `RANDOM`은 다른 요일과 함께 보낼 수 없다. 값은 RANDOM 단독 또는 하나 이상의 실제 요일 목록이어야 한다. |
|
||
| SERIES-007 | 확정 | 장르는 이름 검색형 선택기로 고르고 API에는 `genreId`를 보낸다. |
|
||
| SERIES-008 | 확정 | 활성 시리즈 전체를 별도 순서 변경 모드에서 불러와 최종 순서의 모든 `seriesIds`를 전송한다. |
|
||
| SERIES-009 | 확정 | drag-and-drop 외에 키보드와 위/아래 버튼으로 순서를 바꿀 수 있어야 한다. |
|
||
| SERIES-010 | 확정 | 일반 수정 요청에서는 `isActive`를 생략하고 soft delete 요청에만 `isActive=false`를 보낸다. `isActive=true`, 복원과 hard delete는 제공하지 않는다. |
|
||
| SERIES-011 | 외부 의존 | 장르 이름 검색 API는 백엔드가 제공해야 한다. 제공 전에는 장르 선택 network integration을 구현하지 않는다. 생성 초기 state의 정확한 기본값은 프론트엔드 의존사항이 아니다. |
|
||
| SERIES-012 | 확정 | 시리즈 목록은 활성 시리즈만 반환하며 활성 상태 filter/query를 제공하지 않는다. |
|
||
| SERIES-013 | 확정 | 시리즈 soft delete 성공 시 선택 캐릭터의 시리즈 active-only 목록으로 이동하고 성공 알림을 표시한다. 현재 상세 화면에 머물지 않는다. |
|
||
|
||
#### 시리즈 콘텐츠 연결
|
||
|
||
- 현재 연결 콘텐츠를 검색·페이지네이션해 보여준다.
|
||
- 연결 후보는 선택 캐릭터의 활성 오디오로 제한한다.
|
||
- 이미 연결된 콘텐츠를 중복 연결하지 않는다.
|
||
- 연결 해제 전 대상 제목과 영향을 확인한다.
|
||
- 연결/해제 성공 후 시리즈 상세와 콘텐츠 목록을 함께 갱신한다.
|
||
|
||
### 8.5 커뮤니티 게시글
|
||
|
||
| ID | 상태 | 요구사항 |
|
||
|---|---|---|
|
||
| COMMUNITY-001 | 확정 | 선택 캐릭터의 게시글 목록 기반 조회·등록·수정·고정·비활성화를 제공한다. |
|
||
| COMMUNITY-002 | 확정 | 생성 요청에는 `isActive`를 보내지 않는다. |
|
||
| COMMUNITY-003 | 확정 | 이미지와 오디오 파일은 선택 첨부다. |
|
||
| COMMUNITY-004 | 확정 | 첨부 오디오가 있으면 목록 행/카드와 게시글 Sheet에서 재생할 수 있다. |
|
||
| COMMUNITY-005 | 확정 | 일반 수정 요청에서는 `isActive`를 생략하고 soft delete 요청에만 `isActive=false`를 보낸다. `isActive=true`는 전송하지 않는다. |
|
||
| COMMUNITY-006 | 확정 | 비활성 게시글은 반드시 `isFixed=false`, `fixedAtUtc=null` 상태여야 한다. |
|
||
| COMMUNITY-007 | 확정 | 가격은 오디오와 동일하게 0 이상의 정수 “캔” 단위를 사용한다. |
|
||
| COMMUNITY-008 | 확정 | 커뮤니티 게시글 목록은 활성 게시글만 반환하며 활성 상태 filter/query를 제공하지 않는다. |
|
||
| COMMUNITY-009 | 확정 | 커뮤니티 전용 상세 GET과 상세·수정 직접 route를 추가하지 않는다. 목록 응답으로 행/카드의 Sheet를 열어 조회·수정·고정·비활성화·댓글 진입을 제공한다. |
|
||
| COMMUNITY-010 | 확정 | 커뮤니티 게시글 soft delete 성공 시 열린 Sheet를 닫고 게시글 active-only 목록을 무효화·재조회해 해당 항목을 제거하며 성공 알림을 표시한다. |
|
||
| COMMUNITY-011 | 확정 | 첨부 audio URL 갱신만을 위한 자동 요청은 하지 않으며 media error도 refetch trigger로 사용하지 않는다. 사용자 페이지 새로고침이나 mutation 후 cache 무효화 등 일반 목록 재조회가 발생하면 새 응답의 `audioSignedUrl`을 사용한다. |
|
||
|
||
### 8.6 FanTalk
|
||
|
||
| ID | 상태 | 요구사항 |
|
||
|---|---|---|
|
||
| FANTALK-001 | 확정 | 기본 목록은 전체 FanTalk를 최신순으로 표시한다. |
|
||
| FANTALK-002 | 확정 | 필터는 전체, 미답변, 답변 완료 세 가지다. |
|
||
| FANTALK-003 | 확정 | 하나의 FanTalk에는 답변을 한 번만 작성할 수 있다. |
|
||
| FANTALK-004 | 확정 | 답변이 있으면 추가 작성은 차단하고 기존 답변 수정만 허용한다. |
|
||
| FANTALK-005 | 확정 | 답변 삭제는 현재 범위가 아니다. |
|
||
| FANTALK-006 | 확정 | 데스크톱·태블릿·모바일 모두 답변 작성과 수정을 지원한다. |
|
||
| FANTALK-007 | 외부 의존 | 제공된 계약에는 답변 POST만 있다. 목록·상세·답변 수정 endpoint와 DTO는 백엔드가 제공해야 하며, 제공 전에는 해당 network integration을 구현하지 않는다. |
|
||
| FANTALK-008 | 외부 의존 | 답변 1개 불변식의 원자적 강제와 중복 생성의 정확한 비2xx status/message key는 백엔드가 결정·제공해야 한다. |
|
||
|
||
### 8.7 댓글과 답글
|
||
|
||
| ID | 상태 | 요구사항 |
|
||
|---|---|---|
|
||
| COMMENT-001 | 확정 | 오디오 콘텐츠와 커뮤니티 게시글에 댓글 영역을 제공한다. |
|
||
| COMMENT-002 | 확정 | 구조는 루트 댓글과 그 댓글의 직접 답글까지 정확히 2단계다. 답글의 답글은 허용하지 않는다. |
|
||
| COMMENT-003 | 확정 | AI 캐릭터는 루트 댓글과 답글을 작성하고 자신이 작성한 내용을 수정·soft delete할 수 있다. |
|
||
| COMMENT-004 | 확정 | 팬이 작성한 루트 댓글과 답글은 수정할 수 없고 운영 목적의 soft delete만 가능하다. |
|
||
| COMMENT-005 | 확정 | 모바일에서도 조회·작성·수정·soft delete를 모두 지원한다. |
|
||
| COMMENT-006 | 외부 의존 | 댓글 목록·작성·수정·soft delete API와 팬 댓글 삭제 권한 오류 계약은 백엔드가 결정·제공해야 한다. 제공 전에는 댓글 network integration을 구현하지 않는다. |
|
||
|
||
### 8.8 공통 파일 정책
|
||
|
||
| ID | 상태 | 요구사항 |
|
||
|---|---|---|
|
||
| FILE-001 | 확정 | 캐릭터·오디오 cover·시리즈·커뮤니티 image의 최대 크기는 10MB다. |
|
||
| FILE-002 | 확정 | 기본 image 형식은 JPEG(`.jpg`/`.jpeg`, `image/jpeg`)와 PNG(`.png`, `image/png`)다. WebP 등 다른 형식은 허용하지 않는다. |
|
||
| FILE-003 | 확정 | GIF(`.gif`, `image/gif`)는 커뮤니티 image에서만 허용한다. 캐릭터·시리즈·오디오 cover에서는 거부한다. |
|
||
| FILE-004 | 확정 | 커뮤니티 JPEG/PNG image는 자유 aspect ratio로 크롭하며 결과의 최대 가로 폭은 800px, 세로는 선택한 crop ratio에 따라 결정한다. |
|
||
| FILE-005 | 확정 | 시리즈 image는 `210:297` 세로형 고정 aspect ratio로 크롭하며 결과의 최대 가로 폭은 1,000px다. |
|
||
| FILE-006 | 확정 | 오디오 콘텐츠 cover는 `1:1` 고정 aspect ratio로 크롭하며 결과의 최대 가로 폭은 800px다. |
|
||
| FILE-007 | 확정 | 커뮤니티 JPEG/PNG·시리즈·오디오 콘텐츠에서 새 image를 선택하면 업로드 전에 crop UI를 반드시 거친다. 커뮤니티 GIF는 예외다. |
|
||
| FILE-008 | 확정 | crop UI는 이동, 확대/축소, 초기화, 결과 미리보기, 취소, 적용을 제공한다. drag/pinch만 강제하지 않고 키보드와 버튼 대안을 제공한다. |
|
||
| FILE-009 | 확정 | optional 교체 파일 미전송은 기존 media 유지다. crop 취소도 기존 media를 변경하지 않는다. 기존 media 자체 제거는 별도 remove contract가 없어 범위 밖이다. |
|
||
| FILE-010 | 확정 | 캐릭터 image는 JPEG/PNG만 허용하고 `1:1` 고정 aspect ratio로 크롭하며 결과의 최대 가로·세로는 800px다. |
|
||
| FILE-011 | 확정 | 커뮤니티 GIF는 crop하지 않는다. crop Dialog를 열지 않고 원본 비율과 animation을 유지한 File을 등록한다. |
|
||
| FILE-012 | 확정 | JPEG/PNG crop 결과는 선택된 원본 crop 영역의 pixel 크기보다 확대하지 않는다. resource별 800px/1,000px 값은 최대 출력 폭이며 작은 원본은 가능한 원본 크기로 출력한다. |
|
||
| FILE-013 | 확정 | 커뮤니티 첨부 audio는 오디오 콘텐츠와 동일하게 MP3(`.mp3`, `audio/mpeg`), AAC(`.aac`, `audio/aac`), M4A(`.m4a`, `audio/mp4` 또는 `audio/x-m4a`), 최대 `1,024,000,000 bytes`, 재생 길이 제한 없음 정책을 사용하고 WAV는 거부한다. `audio/x-m4a`는 `.m4a`와 실제 container/codec 검증이 일치할 때만 허용한다. |
|
||
| FILE-014 | 확정 | 커뮤니티 GIF의 원본 가로가 800px을 초과하면 등록을 거부한다. client에서 제출 전에 차단하고 server도 같은 제한을 검증한다. GIF를 축소·crop·재인코딩하지 않는다. |
|
||
| FILE-015 | 확정 | Series crop 결과의 세로 pixel은 `round(width × 297 ÷ 210)`으로 계산한다. 최대 폭에서는 1,000×1,414px이며 비율 검증은 계산된 세로값 기준 1px 이내 오차를 허용한다. |
|
||
|
||
“가로 800/1,000”은 이 문서에서 등록 결과의 **최대 출력 폭**으로 해석한다. JPEG/PNG crop 결과에는 이 제한을 적용하되 선택한 원본 crop 영역이 더 작으면 확대하지 않는다. 커뮤니티 GIF는 원본 가로가 800px 이하일 때만 등록할 수 있다.
|
||
|
||
#### Image crop UI 흐름
|
||
|
||
1. 새 image 선택 직후 resource별 MIME과 10MB 제한을 먼저 검증한다.
|
||
2. 커뮤니티 GIF이면 원본 가로를 검사한다. 800px 초과는 inline 오류로 차단하고, 800px 이하는 crop Dialog 없이 원본 비율과 animation을 유지한다.
|
||
3. 캐릭터 image, 커뮤니티 JPEG/PNG, 시리즈 image, 오디오 콘텐츠 cover이면 crop Dialog를 열고 resource별 자유/고정 aspect ratio를 적용한다.
|
||
4. crop Dialog에는 현재 crop 영역과 예상 결과 크기를 표시한다.
|
||
5. “적용” 시 crop 결과 File과 미리보기를 form에 반영하고, “취소” 시 새 선택과 crop 결과를 버려 기존 image 상태를 유지한다.
|
||
6. form 제출 시 crop 대상은 적용된 결과 File을, 커뮤니티 GIF는 crop하지 않은 File을 기존 multipart image part로 보낸다.
|
||
|
||
커뮤니티 GIF는 canvas crop이나 resize 대상이 아니다. client 검증을 우회한 요청도 server가 원본 가로 800px 제한을 다시 검증하고 거부한다.
|
||
|
||
## 9. 반응형 기능 범위
|
||
|
||
| 기능 | 데스크톱 | 태블릿 | 모바일 |
|
||
|---|---:|---:|---:|
|
||
| 로그인·로그아웃 | 전체 | 전체 | 전체 |
|
||
| 캐릭터 목록·검색·상세 조회 | 전체 | 전체 | 조회 |
|
||
| 캐릭터 생성·수정·비활성화 | 전체 | 전체 | 미지원 |
|
||
| 오디오 목록·상세·재생 | 전체 | 전체 | 전체 |
|
||
| 오디오 생성·수정·비활성화·업로드 | 전체 | 전체 | 미지원 |
|
||
| 시리즈 목록·상세·연결 콘텐츠 조회 | 전체 | 전체 | 조회 |
|
||
| 시리즈 생성·수정·비활성화·연결·순서 | 전체 | 전체 | 미지원 |
|
||
| 커뮤니티 목록·게시글 Sheet·오디오 재생 | 전체 | 전체 | 전체 |
|
||
| 커뮤니티 등록·수정·고정·비활성화 | 전체 | 전체 | 미지원 |
|
||
| 댓글·답글 관리 | 전체 | 전체 | 전체 |
|
||
| FanTalk 조회·답변 작성·답변 수정 | 전체 | 전체 | 전체 |
|
||
|
||
모바일에서 미지원인 기능은 좁은 화면에 데스크톱 폼을 억지로 노출하지 않는다. 화면에는 읽기 전용임을 알리고 전체 관리가 필요하면 데스크톱·태블릿 사용을 안내한다.
|
||
|
||
## 10. UI/UX Expectations
|
||
|
||
### 10.1 디자인 방향
|
||
|
||
`ui-ux-pro-max` 검색과 shadcn/ui 패턴을 바탕으로 다음을 초기 설계 기준으로 사용한다. `#00BDF7` main color는 고정하고, 보조 치수와 supporting semantic color는 접근성·실화면 검증에서 같은 역할과 대비를 유지하는 범위 안에서 조정할 수 있다.
|
||
|
||
| ID | 상태 | 요구사항 |
|
||
|---|---|---|
|
||
| UX-001 | 확정 | 초기 릴리스는 밝은 테마만 제공한다. 다크 모드, 테마 전환 버튼과 `prefers-color-scheme: dark` 연동은 후속 범위다. |
|
||
| UX-002 | 확정 | 브랜드 main color와 primary 기준색은 `#00BDF7`이다. 전체 밝은 테마 palette는 이 cyan 계열과 조화되는 semantic token으로 구성하며, raw hex를 feature component에서 직접 사용하지 않는다. |
|
||
|
||
| 항목 | 초기 권고 기준 |
|
||
|---|---|
|
||
| 스타일 | Data-Dense Dashboard + Trust & Authority |
|
||
| 정보 밀도 | 9/10, 조밀하지만 행·필드 그룹은 명확히 구분 |
|
||
| 시각적 변주 | 2/10, 장식보다 상태와 계층 중심 |
|
||
| 모션 | 2/10, 150–200ms의 짧은 상태 전환만 사용 |
|
||
| 기본 모드 | 밝은 테마만 제공 |
|
||
| 기본 간격 | 8px 배수 |
|
||
| 데스크톱 내비게이션 | 약 240px sidebar + 56px header |
|
||
| 표 행 높이 | 최소 44px, 헤더 고정 가능 |
|
||
| 아이콘 | shadcn/ui와 일관된 Lucide 아이콘, emoji 아이콘 금지 |
|
||
|
||
검색 결과에 포함된 landing page 중심의 과장된 대형 타이포그래피, glassmorphism, 지속 애니메이션은 운영 관리자 화면과 맞지 않아 적용하지 않는다.
|
||
|
||
### 10.2 색상과 토큰
|
||
|
||
기능 코드에서 `cyan-600`이나 raw hex처럼 용도를 알 수 없는 값을 직접 반복하지 않는다. primitive → semantic → component의 3단계 token을 사용하고 shadcn/ui CSS variable과 Tailwind semantic color에 연결한다.
|
||
|
||
#### Primary primitive scale
|
||
|
||
`brand-500`은 사용자가 지정한 값을 변경하지 않는다. 옅은 단계는 배경·선택 상태, 짙은 단계는 링크·focus·정보 상태에 사용한다.
|
||
|
||
| Token | 값 | 대표 용도 |
|
||
|---|---|---|
|
||
| `brand-50` | `#F0FBFF` | 아주 옅은 강조 배경 |
|
||
| `brand-100` | `#D9F6FF` | 선택 행, accent 배경 |
|
||
| `brand-200` | `#B5EEFF` | 강조 border |
|
||
| `brand-300` | `#7CE2FF` | 비활성 장식 강조 |
|
||
| `brand-400` | `#36D1FF` | 보조 시각 강조 |
|
||
| `brand-500` | `#00BDF7` | main color, primary 기본 배경 |
|
||
| `brand-600` | `#00A9DE` | primary hover |
|
||
| `brand-700` | `#009DCE` | primary active |
|
||
| `brand-800` | `#007EA8` | 링크, focus ring, info |
|
||
| `brand-900` | `#086789` | 링크 hover |
|
||
| `brand-950` | `#063747` | 가장 짙은 브랜드 강조 |
|
||
|
||
#### 밝은 테마 semantic palette
|
||
|
||
| Semantic token | 기준색 | 사용 |
|
||
|---|---|---|
|
||
| `background` | `#F6FBFD` | 페이지 배경 |
|
||
| `card` / `popover` | `#FFFFFF` | 카드, 표, 폼, overlay surface |
|
||
| `foreground` | `#102A33` | 본문과 제목 |
|
||
| `muted` | `#E9F4F7` | 비강조 surface |
|
||
| `muted-foreground` | `#425F69` | 보조 정보 |
|
||
| `secondary` | `#E1F5FA` | 보조 버튼과 선택 전 control |
|
||
| `secondary-foreground` | `#123E4B` | secondary 위 텍스트 |
|
||
| `accent` | `#D9F6FF` | 선택 행, hover surface |
|
||
| `accent-foreground` | `#0C566F` | accent 위 텍스트 |
|
||
| `border` | `#D5E8EE` | 표 구분선 등 장식 경계 |
|
||
| `input` | `#577581` | 입력과 필수 조작 경계 |
|
||
| `primary` | `#00BDF7` | 주요 CTA와 브랜드 강조 |
|
||
| `primary-hover` | `#00A9DE` | 주요 CTA hover |
|
||
| `primary-active` | `#009DCE` | 주요 CTA pressed/active |
|
||
| `primary-foreground` | `#062B36` | primary 위 텍스트·아이콘 |
|
||
| `link` / `ring` / `info` | `#007EA8` | 흰 배경 링크, focus indicator, 정보 상태 |
|
||
| `link-hover` | `#086789` | 링크 hover |
|
||
| `success` / `OPEN` | `#167347` | 현재 공개, 성공 |
|
||
| `success-surface` | `#EAF8F0` | 성공 Badge·Alert 배경 |
|
||
| `warning` / `SCHEDULED` | `#9A5B00` | 예약 공개, 주의 |
|
||
| `warning-surface` | `#FFF7E6` | 경고 Badge·Alert 배경 |
|
||
| `destructive` | `#B42318` | 비활성화, 실패 |
|
||
| `destructive-surface` | `#FEF0EE` | 오류 Badge·Alert 배경 |
|
||
| `inactive` | `#52636A` | 비활성 상태 |
|
||
| `inactive-surface` | `#EEF3F5` | 비활성 Badge 배경 |
|
||
|
||
- 상태는 색상만으로 전달하지 않고 Badge 텍스트와 아이콘 또는 보조 문구를 함께 사용한다.
|
||
- 일반 텍스트 대비는 4.5:1, 정보를 이해하는 데 필요한 control boundary와 focus indicator는 3:1을 목표로 검증한다. 장식용 divider는 정보 전달 수단으로 사용하지 않는다.
|
||
- `#00BDF7` 위에는 흰색을 사용하지 않고 `primary-foreground=#062B36`을 사용한다. 이 조합은 약 6.84:1이며, `#00BDF7`과 흰색의 약 2.18:1 조합은 텍스트용으로 금지한다.
|
||
- 흰 배경 위 텍스트 링크와 focus ring에는 `brand-500`이 아니라 `brand-800=#007EA8`을 사용한다. `#007EA8`과 흰색은 약 4.62:1이다.
|
||
- 반복되는 상태색은 `success`, `warning`, `destructive`, `inactive` token으로 정의한다.
|
||
- 초기 릴리스에서는 밝은 테마용 semantic token만 구현한다. 다크 token override, ThemeProvider와 테마 전환 control을 만들지 않으며 시스템이 dark mode여도 밝은 테마를 유지한다.
|
||
|
||
### 10.3 타이포그래피
|
||
|
||
- 한국어 가독성을 위해 `Pretendard`, `Noto Sans KR`, `Apple SD Gothic Neo`, `system-ui` 순의 sans-serif stack을 사용한다.
|
||
- 원격 font가 초기 렌더링을 막지 않게 system fallback을 항상 둔다.
|
||
- 페이지 제목 24px, 섹션 제목 18–20px, 본문·표 14px, 보조 정보 12–13px를 기본으로 한다.
|
||
- iOS 입력 확대를 막기 위해 모바일 입력 요소의 실제 font-size는 16px 이상으로 한다.
|
||
- heading에 mono font나 landing page용 48px 이상 크기를 사용하지 않는다.
|
||
|
||
### 10.4 shadcn/ui 구성요소 매핑
|
||
|
||
| UI 목적 | 기본 구성요소 |
|
||
|---|---|
|
||
| 앱 구조 | Sidebar, Sheet, Breadcrumb, Tabs, Separator, ScrollArea |
|
||
| 목록 | Table/Data Table, Card, Badge, Pagination, DropdownMenu |
|
||
| 검색·선택 | Input, Select, Command + Popover Combobox |
|
||
| 폼 | Form, Label, Input, Textarea, Checkbox, RadioGroup, Calendar, Popover |
|
||
| 상태·피드백 | Alert, Skeleton, Progress, Sonner/Toast |
|
||
| 확인·편집 | Dialog, AlertDialog, Drawer |
|
||
| 파일 | Input 기반 공통 FileField + Dialog 기반 ImageCropDialog |
|
||
| 정렬 | 접근 가능한 SortableList와 위/아래 Button |
|
||
| 미디어 | native audio를 감싼 공통 AdminAudioPlayer |
|
||
|
||
- 비활성화처럼 영향이 큰 동작은 Switch가 아니라 AlertDialog를 사용한다.
|
||
- icon-only 버튼에는 `aria-label`과 Tooltip을 제공한다.
|
||
- 모바일의 보조 작업은 DropdownMenu 또는 Drawer에 배치하되 핵심 답변·댓글 동작은 한 번에 찾을 수 있어야 한다.
|
||
- crop Dialog는 pointer drag와 pinch/zoom을 지원하되 이동·확대·축소·초기화를 실행하는 명시적 버튼과 keyboard 조작도 제공한다.
|
||
- crop frame, preview, 적용/취소 control은 tablet touch target 44×44px 이상과 보이는 label 또는 accessible name을 가진다.
|
||
|
||
### 10.5 화면 상태
|
||
|
||
모든 목록과 상세 화면은 다음 상태를 명시적으로 가진다.
|
||
|
||
- 첫 로딩: 레이아웃과 유사한 Skeleton
|
||
- 백그라운드 갱신: 기존 데이터를 유지하고 작은 진행 표시
|
||
- 빈 결과: 현재 검색·필터를 설명하고 초기화 동작 제공
|
||
- 오류: 서버의 한국어 message, 재시도, 필요한 경우 목록으로 이동
|
||
- 저장 중: 제출 버튼 비활성화와 진행 표시
|
||
- 저장 성공: toast와 최신 서버 응답 반영
|
||
- soft delete 성공: 해당 resource의 active-only 목록으로 이동하고 성공 toast 표시
|
||
- 필드 오류: 로컬 validation은 해당 필드 아래에 표시하고 `aria-invalid`, 오류와 입력 연결, 첫 오류 focus를 제공한다. 서버 오류는 `errorProperty`가 실제 필드명을 제공하는 계약일 때만 inline으로 연결한다.
|
||
- 업로드: 파일별 진행률, 취소, 재시도
|
||
|
||
300ms 이상 걸릴 수 있는 작업에는 시각적 피드백을 제공하며, 연속 장식 애니메이션은 사용하지 않는다.
|
||
|
||
### 10.6 반응형 원칙
|
||
|
||
- Tailwind의 mobile-first breakpoints를 사용하고 한 요소에 2–3개를 넘는 불필요한 breakpoint를 피한다.
|
||
- 320, 640, 768, 1024, 1280px와 가로 방향을 검증한다.
|
||
- 데스크톱 표는 모바일에서 핵심 필드 중심 Card 목록으로 전환한다. 단순 horizontal scroll만으로 핵심 동작을 숨기지 않는다.
|
||
- 모바일 터치 target은 최소 44×44px이다.
|
||
- 좁은 화면에서 가로 넘침, 잘린 dialog, keyboard에 가려진 답변 입력이 없어야 한다.
|
||
- 데스크톱 sidebar는 모바일에서 Sheet 내비게이션으로 바뀐다.
|
||
|
||
### 10.7 접근성 최소 기준
|
||
|
||
- semantic HTML과 올바른 button/link를 사용한다.
|
||
- “본문으로 건너뛰기” 링크와 `main` landmark를 제공한다.
|
||
- 모든 입력에 보이는 Label을 제공하고 placeholder를 Label 대신 사용하지 않는다.
|
||
- 키보드만으로 메뉴, 탭, 표 행 동작, dialog, 정렬, 답변 작성이 가능해야 한다.
|
||
- focus indicator를 제거하지 않는다.
|
||
- dialog focus trap, 닫힌 후 trigger로 focus 복귀를 검증한다.
|
||
- 비동기 결과와 오류는 적절한 live region 또는 shadcn toast로 전달한다.
|
||
- `prefers-reduced-motion`을 존중한다.
|
||
- 200% browser zoom에서도 핵심 기능을 사용할 수 있어야 한다.
|
||
|
||
### 10.8 z-index와 overlay
|
||
|
||
임의의 `z-[9999]`를 사용하지 않고 semantic scale을 둔다.
|
||
|
||
| 단계 | 값 | 예 |
|
||
|---|---:|---|
|
||
| sticky | 10 | table header |
|
||
| navigation | 20 | header, sidebar |
|
||
| popover | 30 | select, dropdown, date picker |
|
||
| overlay | 40 | sheet/dialog backdrop |
|
||
| modal/notification | 50 | dialog content, toast |
|
||
|
||
Radix Portal과 stacking context를 함께 검증해 Popover가 Dialog 뒤로 숨지 않게 한다.
|
||
|
||
### 10.9 ui-ux-pro-max 사용 의무
|
||
|
||
UI 구현자는 화면 생성 전에 저장소의 `ui-ux-pro-max` design-system 검색을 실행하고 결과를 이 문서의 디자인 방향과 대조한다.
|
||
|
||
```bash
|
||
python3 .codex/skills/ui-ux-pro-max/scripts/search.py \
|
||
"enterprise internal admin console data tables forms file upload operational dashboard neutral compact" \
|
||
--design-system --variance 2 --motion 2 --density 9 \
|
||
-p "AI Character Admin" -f markdown
|
||
```
|
||
|
||
구현 중에는 필요한 domain 검색을 수행하고, 화면 검증 전에 다음 UX 검색을 다시 실행한다.
|
||
|
||
```bash
|
||
python3 .codex/skills/ui-ux-pro-max/scripts/search.py \
|
||
"animation accessibility z-index loading" --domain ux -n 12
|
||
```
|
||
|
||
검색 결과가 관리자 제품의 목적과 충돌하면 그대로 적용하지 않고, 채택·제외 이유를 PR 또는 작업 기록에 남긴다.
|
||
|
||
## 11. API 계약
|
||
|
||
### 11.1 공통 응답
|
||
|
||
모든 성공 응답은 `ApiResponse.ok(...)` wrapper를 사용한다.
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": null,
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
`/admin/member/login`과 `/member/logout` 성공 예시는 최상위 `errorProperty=null`도 포함한다. 공통 API client는 성공 응답에서 `errorProperty`가 없거나 `null`인 두 형태를 모두 수용한다.
|
||
|
||
모든 오류는 의미에 맞는 비2xx status와 `ApiResponse.error(...)` wrapper를 사용한다.
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "잘못된 요청입니다.",
|
||
"data": null,
|
||
"errorProperty": null
|
||
}
|
||
```
|
||
|
||
- 오류를 2xx로 normalize하지 않는다.
|
||
- UI는 서버가 반환한 현지화된 한국어 `message`를 우선 사용한다.
|
||
- 백엔드는 `Accept-Language: ko|en|ja`에 따라 번역하고 누락·미지원 언어는 KO로 fallback한다. security filter도 header를 직접 해석한다.
|
||
- 모든 목록·검색 endpoint는 `page=0`, `size=20` 기본값과 size 최소 20, 최대 50 보정을 적용한다.
|
||
- `characterId`는 선택된 대상 캐릭터가 필요한 하위 resource endpoint에만 사용한다.
|
||
- 캐릭터 목록·검색과 캐릭터 생성에는 path `characterId`가 없다.
|
||
|
||
### 11.2 오류 처리 매핑
|
||
|
||
| HTTP | 의미 | UI 처리 |
|
||
|---:|---|---|
|
||
| 400 | binding, target 미존재, creator 불변식 등 invalid request | 로컬 검증은 inline, 서버 `errorProperty`가 필드명을 제공할 때만 inline, 그 외 화면 Alert |
|
||
| 401 | JWT 없음·잘못됨·만료·폐기 | 인증 제거 후 로그인 이동 |
|
||
| 403 | 비ADMIN 또는 stale ADMIN claim | 접근 거부 화면 |
|
||
| 404 | 신규 prefix 미매핑 경로 | 찾을 수 없음과 목록 이동 |
|
||
| 405 | 지원하지 않는 method | 서버 message와 재시도 불가 안내 |
|
||
| 415 | 지원하지 않는 media type | 파일 필드 오류와 허용 형식 안내 |
|
||
| 500 | 예상하지 못한 오류 | 일반 오류, request ID가 있으면 함께 표시, 재시도 |
|
||
|
||
Phase 2~6에서 추가되는 오류는 구현 전에 정확한 비2xx status와 KO/EN/JA message key를 확정해야 한다. 신규 prefix 전용 처리를 legacy/public endpoint로 확장하지 않는다.
|
||
|
||
### 11.3 제공된 endpoint 목록
|
||
|
||
모든 query, multipart part, request/response field를 보존한 보정 후 계약은 [api-contract.md](./api-contract.md)를 기준으로 한다.
|
||
|
||
| 영역 | Method | Path | 계약 상태 |
|
||
|---|---|---|---|
|
||
| 인증 | POST | `/admin/member/login` | 제공됨, body는 email/password, 성공 시 token/ADMIN role |
|
||
| 인증 | POST | `/member/logout` | 제공됨, 공통 endpoint, Bearer header, body 없음 |
|
||
| 캐릭터 | GET, POST | `/api/v2/admin/ai-characters` | 제공됨, 필드 보정 필요 |
|
||
| 캐릭터 | GET, PUT | `/api/v2/admin/ai-characters/{characterId}` | 제공됨, 필드 보정 필요 |
|
||
| 오디오 | GET, POST | `/api/v2/admin/ai-characters/{characterId}/audio-contents` | 제공됨, 생성 필드 보정 필요 |
|
||
| 오디오 | GET, PUT | `.../audio-contents/{contentId}` | 제공됨 |
|
||
| 시리즈 | GET, POST | `/api/v2/admin/ai-characters/{characterId}/series` | 제공됨, enum 보정 필요 |
|
||
| 시리즈 | GET, PUT | `.../series/{seriesId}` | 제공됨, enum 보정 필요 |
|
||
| 시리즈 콘텐츠 | GET, POST | `.../series/{seriesId}/contents` | 제공됨 |
|
||
| 시리즈 콘텐츠 | DELETE | `.../series/{seriesId}/contents/{contentId}` | 제공됨 |
|
||
| 시리즈 순서 | PUT | `.../series/orders` | 제공됨 |
|
||
| 커뮤니티 | GET, POST | `.../{characterId}/community-posts` | 제공됨, 생성 필드 보정 필요 |
|
||
| 커뮤니티 | PUT | `.../community-posts/{postId}` | 제공됨 |
|
||
| FanTalk 답변 | POST | `.../{characterId}/fan-talks/{fanTalkId}/replies` | 제공됨 |
|
||
|
||
### 11.4 API 계약 보정사항
|
||
|
||
이 표는 최초 API Contract보다 우선한다.
|
||
|
||
| 항목 | 최초 계약 | 확정 보정 |
|
||
|---|---|---|
|
||
| Character `externalCharacterId` | 요청·응답에 존재 | 존재하지 않는 필드이므로 전부 제거 |
|
||
| 생성 `isActive` | Character, Audio, Community 예시에 존재 | 모든 생성 요청에서 제거, 서버가 초기값 결정 |
|
||
| 수정 `isActive` | 수정 예시에 존재 | 일반 수정에서는 key를 생략하고 soft delete에만 `false`를 보낸다. `true`는 전송하지 않는다. |
|
||
| Audio status | 예시에 `OPEN` | 허용값은 `OPEN`, `SCHEDULED`이며 서버 계산 |
|
||
| Series state | 예시에 `OPEN` | `PROCEEDING`, `SUSPEND`, `COMPLETE`만 허용 |
|
||
| Series 생성 state | 요청 예시에 `OPEN` | 생성 요청에서 state 제거 |
|
||
| Series 수정 state | 필수처럼 표현 | 선택 필드, 미선택 시 생략하여 기존 값 유지 |
|
||
| Series 요일 | `MONDAY` 등 장문 값 | `SUN`~`SAT`와 `RANDOM` 사용 |
|
||
| RANDOM | 규칙 없음 | 단독만 허용, 다른 요일과 조합 금지 |
|
||
| Character creator | 응답 연결만 표현 | 생성 시 AI_CHARACTER creator 동시 생성, 프로필 동기화는 백엔드 담당 |
|
||
| FanTalk 답변 | POST만 표현 | 한 번만 생성, 기존 답변 수정 가능, 삭제 불가 |
|
||
|
||
### 11.5 백엔드 제공 대기 계약
|
||
|
||
아래 항목은 프론트엔드 인터뷰로 결정할 Open Question이 아니라 **외부 의존**이다. P0의 request/response/error 계약을 제공받기 전에는 해당 기능의 network integration을 구현하지 않는다. P1은 추정하지 않고 출시 전 계약과 검증을 맞춘다. UI shell과 계약에 의존하지 않는 표현 작업은 병행할 수 있다.
|
||
|
||
| 우선순위 | 백엔드 제공 필요 계약 | 프론트엔드 영향 |
|
||
|---:|---|---|
|
||
| P0 | FanTalk 목록·상세·답변 수정·유일성 | 목록·상세·수정 연동과 동시 중복 답변 처리 대기 |
|
||
| P0 | 오디오·커뮤니티 댓글 CRUD와 팬 댓글 삭제 권한 오류 | 댓글·답글 연동과 권한별 오류 처리 대기 |
|
||
| P0 | 원작·장르 검색과 originalWork 미선택 직렬화 | Character·Series 선택기 연동 대기 |
|
||
| P0 | 시리즈 연결 후보 | 연결 가능한 활성 오디오 선택기 연동 대기 |
|
||
| P0 | 시리즈 전체 순서의 50개 초과 로딩·누락 ID·동시 충돌 | 전체 순서 저장 연동 대기 |
|
||
| P1 | price 최대값 | 현재 0 이상 정수 규칙만 적용하며 상한 계약 제공 시 Audio·Community schema와 경계값 test 갱신 |
|
||
| P0 | 신규 도메인 오류 | 기능별 정확한 비2xx status와 KO/EN/JA message key 제공 대기 |
|
||
|
||
## 12. 보안과 데이터 취급
|
||
|
||
- JWT, 비밀번호, signed URL, 업로드 파일 본문을 console·분석 이벤트·오류 리포트에 남기지 않는다.
|
||
- 로그인 요청은 인증 header 없이 `POST /admin/member/login`을 호출한다.
|
||
- 로그인 응답의 `token`은 보호 API와 `POST /member/logout`에 `Authorization: Bearer {jwt-token}` 형식으로 적용한다.
|
||
- 로그인 응답의 JWT와 ADMIN role은 `sessionStorage`에만 저장하고 `localStorage`, IndexedDB 또는 cookie로 복제하지 않는다. 같은 탭의 새로고침에서는 복원하며 로그아웃과 401 처리 시 제거한다.
|
||
- 로그아웃 요청은 현재 Bearer token으로 한 번 호출한다. 성공 여부와 관계없이 로컬 인증 정보를 제거하고 로그인 화면으로 이동하며, 네트워크·비2xx 실패 시 서버 로그아웃 확인 실패 경고를 표시한다.
|
||
- API client는 Bearer header와 `Accept-Language: ko`를 중앙에서 일관되게 적용한다.
|
||
- 401 처리 중 여러 요청이 동시에 실패해도 로그인 이동과 알림을 한 번만 수행한다.
|
||
- 파일 이름은 화면 표시에만 사용하고 경로나 MIME을 신뢰하지 않는다.
|
||
- 캐릭터 하위 mutation은 URL의 `characterId`와 서버 ownership 검증을 모두 통과해야 한다.
|
||
- 워크스페이스 진입·복원용 상세 성공 응답으로 `isActive=false`인 캐릭터를 받은 경우 해당 character workspace의 mutation 진입점을 차단한다. soft delete mutation 성공 직후에는 목록 이동을 우선한다. 비활성 ID 조회 허용 여부와 서버의 mutation 검증 정책은 백엔드 책임이다.
|
||
|
||
### 감사 로그 미결 사항과 권고
|
||
|
||
현재 감사 로그 정책은 **미결**이다.
|
||
|
||
**권고:** 이번 범위에서는 백엔드가 관리자 ID, 대상 캐릭터 ID, resource 종류와 ID, action, 성공/실패, 서버 시각, request ID, 민감정보를 제거한 변경 요약을 기록한다. 관리자 UI의 감사 로그 조회 화면은 후속 범위로 둔다. JWT, 비밀번호, signed URL, 파일 본문은 기록하지 않는다.
|
||
|
||
## 13. 성능과 품질 요구사항
|
||
|
||
- 목록은 서버 페이지네이션을 사용하고 무제한 전체 로드를 피한다. 단, 시리즈 순서 변경 모드는 계약상 활성 시리즈 전체를 명시적으로 로드한다.
|
||
- 이미지에는 고정 aspect ratio와 크기를 예약해 layout shift를 줄이고 목록 이미지는 lazy load한다.
|
||
- 필터 변경 중 기존 데이터를 유지해 화면 깜빡임을 줄인다.
|
||
- 파일 업로드 외의 일반 mutation은 중복 제출을 막는다.
|
||
- 목록 검색 debounce 시간은 구현 시 300ms 전후로 일관되게 적용한다.
|
||
- 날짜, 가격, 상태 label은 중앙 formatter로 일관되게 표시한다.
|
||
- 브라우저 지원 범위는 데스크톱 Chrome/Edge/Safari 최신 2개 주요 버전과 모바일 Chrome/Safari 최신 2개 주요 버전이다.
|
||
|
||
## 14. 성공 기준
|
||
|
||
### 14.1 기능 수용 기준
|
||
|
||
- ADMIN이 로그인해 캐릭터를 검색하고 선택할 수 있다.
|
||
- 로그인 요청이 `POST /admin/member/login`에 email/password만 보내고 ADMIN role과 token을 처리한다.
|
||
- 로그인 성공 후 JWT와 ADMIN role이 `sessionStorage`에만 저장되어 같은 탭의 새로고침에서 복원되고, `localStorage`, IndexedDB, cookie에는 기록되지 않으며 로그아웃과 401에서 제거된다.
|
||
- 로그아웃 요청이 관리자 전용 경로가 아닌 `POST /member/logout`에 Bearer header와 body 없이 전송된다.
|
||
- 로그아웃 API가 성공하거나 네트워크·비2xx 오류로 실패해도 로컬 session이 제거되고 로그인 화면으로 이동하며, 실패한 경우 경고가 표시되고 session이 복원되지 않는다.
|
||
- 캐릭터 생성 요청에 `isActive`와 `externalCharacterId`가 포함되지 않는다.
|
||
- Character, Audio, Series, Community의 일반 수정은 `isActive`를 생략하고 soft delete에만 `isActive=false`를 보내며 `true`는 전송하지 않는다.
|
||
- Character, Audio, Series의 soft delete가 성공하면 해당 active-only 목록 cache를 갱신해 비활성화한 항목을 표시하지 않고 목록으로 이동하며 성공 알림을 표시한다. Community는 열린 Sheet를 닫고 현재 active-only 목록을 갱신해 해당 항목을 제거한다.
|
||
- 캐릭터 수정이 creator profile 동기화 결과와 함께 다시 조회된다.
|
||
- 워크스페이스 진입·복원용 상세 성공 응답으로 `isActive=false`인 캐릭터를 받은 경우 프론트엔드가 모든 하위 mutation 진입점을 차단한다. soft delete 성공 직후에는 active-only 목록 이동을 우선하고, 상세 요청의 비2xx 응답은 공통 오류 처리로 표시한다.
|
||
- 오디오 생성에서 즉시 공개는 `releaseDateUtc=null`, 예약 공개는 미래 UTC 시각을 보낸다.
|
||
- MP3, AAC, M4A 업로드의 진행률·취소·재시도와 `1,024,000,000 bytes` 허용·`1,024,000,001 bytes` 거부 경계 검증이 동작한다.
|
||
- 오디오 콘텐츠와 커뮤니티 첨부 audio가 동일한 확장자·MIME·최대 크기·재생 길이 정책을 사용한다.
|
||
- `.m4a`는 `audio/mp4`와 `audio/x-m4a`를 허용하되 호환 MIME도 실제 MP4/M4A container·codec 검증을 통과해야 한다.
|
||
- 모든 image upload가 10MB와 resource별 JPEG/PNG/GIF 허용 범위를 적용한다.
|
||
- 캐릭터 image는 `1:1`로 크롭하고 최대 800×800px 결과를 업로드한다.
|
||
- 커뮤니티 JPEG/PNG는 자유 비율·최대 800px, Series는 `210:297`·최대 1,000px, Audio cover는 `1:1`·최대 800px crop 결과를 업로드한다.
|
||
- JPEG/PNG crop 영역이 resource별 최대 출력 폭보다 작으면 확대하지 않고 가능한 원본 pixel 크기로 업로드한다.
|
||
- Series crop 결과는 `height = round(width × 297 ÷ 210)`을 사용하며 최대 출력은 1,000×1,414px이고 검증 시 1px 이내 오차를 허용한다.
|
||
- 커뮤니티 GIF는 crop Dialog를 열지 않고 원본 비율과 animation을 유지해 등록하며, 원본 가로가 800px을 초과하면 제출 전에 거부한다.
|
||
- crop Dialog에서 이동·확대/축소·초기화·미리보기·취소/적용을 keyboard와 pointer로 완료할 수 있다.
|
||
- 오디오 목록·상세와 커뮤니티 목록 항목의 첨부 오디오가 재생된다.
|
||
- media error를 signed URL 만료로 구분하지 않고 일반 재생 오류를 표시한다. 오류만으로 API를 자동 재조회하거나 자동 재생하지 않으며 사용자가 수동 재시도하거나 페이지를 새로고침할 수 있다.
|
||
- 커뮤니티는 전용 상세 GET이나 상세·수정 route 없이 목록 응답 기반 Sheet에서 조회·수정·고정·비활성화·댓글 진입을 제공한다.
|
||
- Series에 `OPEN`이나 `MONDAY` 같은 잘못된 값을 보내지 않는다.
|
||
- Series 생성에는 state를 보내지 않고 수정 미선택 시 state를 생략한다.
|
||
- RANDOM과 실제 요일을 동시에 선택할 수 없다.
|
||
- FanTalk 답변이 있으면 두 번째 POST가 UI에서 차단되고 수정 동작만 제공되며, 직접·동시 요청도 백엔드가 원자적으로 거부한다.
|
||
- 댓글은 2단계를 넘지 않고 작성자에 따른 수정·삭제 권한이 구분된다.
|
||
- 모바일에서 조회·오디오 재생·댓글 관리·FanTalk 답변 작성/수정이 가능하다.
|
||
|
||
### 14.2 UI/UX 수용 기준
|
||
|
||
- 모든 화면에 로딩, 빈 결과, 오류, 성공 상태가 있다.
|
||
- 모든 폼 입력은 보이는 label과 연결된 오류를 가진다.
|
||
- keyboard-only로 로그인, 캐릭터 선택, 탭 이동, FanTalk 답변, 댓글 관리가 가능하다.
|
||
- 320px에서 가로 넘침 없이 합의된 모바일 기능을 사용할 수 있다.
|
||
- 주요 touch target이 44×44px 이상이다.
|
||
- 상태 정보가 색상에만 의존하지 않는다.
|
||
- primary 기준색은 `#00BDF7`이고 그 위 텍스트·아이콘은 `#062B36`을 사용한다. 흰 배경의 링크와 focus ring은 `#007EA8`을 사용하며 핵심 foreground/background 조합이 WCAG 대비 기준을 충족한다.
|
||
- 초기 릴리스에는 다크 모드와 테마 전환 control이 없고, 시스템 색상 테마와 관계없이 접근성 검증을 통과한 밝은 token을 사용한다.
|
||
- `prefers-reduced-motion`에서 불필요한 transition이 제거된다.
|
||
- 모든 핵심 route의 axe 기반 자동 검사에서 critical·serious 접근성 위반이 0건이다.
|
||
- `ui-ux-pro-max`의 loading, reduced motion, z-index, touch 검증 항목을 확인한다.
|
||
|
||
## 15. Open Questions
|
||
|
||
| ID | 상태 | 결정 필요 사항 | 현재 권고 |
|
||
|---|---|---|---|
|
||
| OQ-009 | 미결 | 각 페이지의 문자열 최대 길이와 배열 최대 개수 | 초기 UI를 만든 뒤 Character의 이름·설명, Audio의 제목·설명·`seriesIds`, Series의 제목·소개·요일·keywords·writer·studio·연결 `contentIds`·순서 `seriesIds`, Community 본문, FanTalk 답변과 댓글을 페이지별로 검토해 권고값을 작성한다. 백엔드 호환 확인 후 확정하며, 그 전에는 제공 계약에 없는 임의의 최대값을 추가하지 않는다. 확정 시 PRD·API Contract·schema·경계값 test를 함께 갱신한다. |
|
||
| OQ-010 | 미결 | 감사 로그 UI 제공 여부 | backend 기록 우선, 조회 UI는 후속 범위 |
|
||
|
||
## 16. 결정 기록
|
||
|
||
| 날짜 | 결정 |
|
||
|---|---|
|
||
| 2026-07-25 | 독립 관리자 웹, 자체 이메일/비밀번호 인증, ADMIN JWT만 사용한다. |
|
||
| 2026-07-25 | refresh token과 자동 갱신 없이 401에서 로그아웃 처리한다. |
|
||
| 2026-07-25 | 로그인은 `POST /admin/member/login`, 로그아웃은 공통 `POST /member/logout`을 사용하고 JWT는 Bearer header로 전달한다. |
|
||
| 2026-07-25 | JWT와 ADMIN role은 `sessionStorage`에만 저장해 같은 탭의 새로고침에서 복원하고 탭 종료 시 제거한다. `localStorage`, IndexedDB, cookie에는 저장하지 않는다. |
|
||
| 2026-07-25 | 로그아웃 API가 실패해도 로컬 session을 제거하고 로그인 화면으로 이동하며 서버 로그아웃 확인 실패 경고를 표시한다. |
|
||
| 2026-07-25 | 관리자는 선택한 캐릭터 문맥에서 작업하며 캐릭터 계정으로 전환하지 않는다. |
|
||
| 2026-07-25 | 생성과 일반 수정 요청에는 `isActive`를 보내지 않고 soft delete 요청에만 `isActive=false`를 보낸다. `isActive=true`는 전송하지 않는다. |
|
||
| 2026-07-25 | Character, Audio, Series, Community 목록은 활성 resource만 반환하고 활성 상태 filter/query를 제공하지 않는다. |
|
||
| 2026-07-25 | Character, Audio, Series soft delete 성공 후 해당 active-only 목록으로 이동하고, Community는 열린 Sheet를 닫고 현재 active-only 목록을 갱신하며 모두 성공 알림을 표시한다. |
|
||
| 2026-07-25 | 비활성 ID 상세 GET의 반환·거부 정책은 백엔드 책임이므로 프론트엔드 요구사항·P0 Gate에서 제외한다. 프론트엔드는 실제 성공 또는 비2xx 응답을 공통 규칙대로 처리한다. |
|
||
| 2026-07-25 | `externalCharacterId`를 모든 계약에서 제거한다. |
|
||
| 2026-07-25 | 오디오 상태는 `OPEN`과 `SCHEDULED`이며 서버가 계산한다. |
|
||
| 2026-07-25 | 즉시 공개는 null, 예약 공개만 날짜를 입력하고 UTC로 전송한다. |
|
||
| 2026-07-25 | MP3, AAC, M4A, 최대 decimal 1,024MB(`1,024,000,000 bytes`), 재생 길이 무제한을 사용한다. |
|
||
| 2026-07-25 | `audio/x-m4a`는 `.m4a` 파일에만 허용하고 실제 MP4/M4A container·codec을 검증한다. |
|
||
| 2026-07-25 | 커뮤니티 첨부 audio도 오디오 콘텐츠와 같은 MP3/AAC/M4A, 최대 1,024MB, 재생 길이 무제한 정책을 사용한다. |
|
||
| 2026-07-25 | 공통 image upload 최대 크기는 10MB로 한다. |
|
||
| 2026-07-25 | image는 JPEG/PNG를 허용하고 GIF는 커뮤니티에만 허용한다. Character는 1:1·800px, Community JPEG/PNG는 자유 비율·800px, Series는 210:297·1,000px, Audio cover는 1:1·800px crop UI를 제공한다. Community GIF는 crop·resize하지 않고 원본 가로 800px 초과 시 등록을 거부한다. |
|
||
| 2026-07-25 | JPEG/PNG crop 결과는 선택한 원본 crop 영역보다 확대하지 않고 resource별 800px/1,000px을 최대 출력 폭으로만 사용한다. |
|
||
| 2026-07-25 | Series crop 세로는 `round(width × 297 ÷ 210)`으로 계산하고 최대 1,000×1,414px, 검증 오차 1px을 적용한다. |
|
||
| 2026-07-25 | Series state와 요일 enum을 실제 backend enum에 맞게 정정한다. |
|
||
| 2026-07-25 | FanTalk는 한 번만 답변할 수 있고 기존 답변 수정은 허용한다. |
|
||
| 2026-07-25 | 모바일은 조회, 오디오 재생, 댓글 전체 관리, FanTalk 답변 작성·수정을 지원한다. |
|
||
| 2026-07-25 | Tailwind CSS와 shadcn/ui를 사용하고 `ui-ux-pro-max`로 UI를 생성·검증한다. |
|
||
| 2026-07-26 | 초기 릴리스는 밝은 테마만 제공하고 다크 모드, 테마 전환 버튼과 시스템 색상 테마 연동은 후속 범위로 둔다. |
|
||
| 2026-07-26 | main color와 primary 기준색을 `#00BDF7`로 정하고 이에 맞춘 cyan 계열 primitive·semantic palette를 사용한다. primary 위에는 접근 가능한 `#062B36`을, 흰 배경의 링크·focus에는 `#007EA8`을 사용한다. |
|
||
| 2026-07-26 | Series 생성 request에는 state를 보내지 않으며 프론트엔드는 서버의 정확한 초기 기본값을 알 필요 없이 생성 응답의 state를 표시한다. |
|
||
| 2026-07-26 | 재생 오류를 signed URL 만료로 구분하지 않고, media error만으로 API 자동 재조회나 자동 재생을 수행하지 않는다. 일반 오류와 수동 재시도·페이지 새로고침 안내를 제공한다. |
|
||
| 2026-07-26 | 커뮤니티 전용 상세 GET과 상세·수정 route를 추가하지 않는다. 목록 응답 기반 Sheet를 사용하고 첨부 audio URL 갱신 전용 요청은 만들지 않는다. 사용자 새로고침이나 mutation 후 cache 무효화 등 일반 목록 재조회가 있으면 새 응답 값을 사용한다. |
|
||
| 2026-07-26 | 문자열 최대 길이와 배열 최대 개수는 초기 UI 작성 후 각 페이지에서 권고값을 정하고 백엔드 호환 확인 후 확정한다. 그 전에는 제공 계약에 없는 최대값을 추정하지 않는다. |
|
||
| 2026-07-26 | 댓글 API·팬 댓글 삭제 권한 오류와 FanTalk 목록·상세·답변 수정·중복 답변 계약은 백엔드 제공 대기 사항으로 분류하고 프론트엔드 Open Questions에서 제외한다. |
|