FeaturesHQ (본사)HQ Mode 라이브러리 조회 (apps/space /admin/libraries)

HQ Mode — 본사 라이브러리 조회 (apps/space /admin/libraries, read-only)

SPEC #080 목록 도입 · #107 상세 도입 (read-only). 실 HQ_MANAGER 직접 로그인 표면(apps/space · space.linkmusic.io) — 운영사 임퍼소네이션(apps/admin)이 아니다.

이 페이지는 apps/space 의 실 HQ_MANAGER 본사 모드다. 운영사(OPERATOR)가 라이브러리를 만들고 음원을 할당하는 apps/admin /libraries(#053)와는 별개다. 음악 2계층은 “운영사 편집 · 본사 조회”가 모델 불변식 — 본 슬라이스가 그 조회 쪽 라이브러리 층을 채운다(편집·음원 할당은 운영사 전용). 본사 PL 조회(#057)와 같은 패턴.

Overview

HQ_MANAGER 가 apps/space 본사 모드에서 자기 본사 PL 에 담을 후보 라이브러리 목록을 조회 한다(read-only). 가시 범위는 운영사가 만든 모든 라이브러리(plan/type mismatch 게이트는 후속 #060 F1) — 본사가 자기 플랜에 맞는 라이브러리를 골라 PL 에 추가하는 패턴(이미 #057 본사 PL 도 같은 패턴). 생성/편집/삭제·음원 할당은 운영사(apps/admin) 전용 — 본사 모드는 mutating 진입점이 없다.

시안 출처: workspace parent dir — hq-library 전용 시안 부재로 본사 모드 기존 화면 (design/screens/hq-stores.jsx 테이블·검색·페이지네이션, hq-playlist 목록 #057) 스타일과 일관 (임의 디자인 금지). 검색·페이지네이션은 공용 ListToolbar/ListPagination(본사 /stores #051 관용구)으로 정합.

목록 (/admin/libraries)

apps/space/src/app/admin/libraries/page.tsx(server 셸) + hq-library-list-client.tsx(client). 본사 /admin/playlists(#057)와 동일 이유로 서버사이드 페이지네이션 + client-query (useListHqLibraries) — q(이름)·type(AI/TRUST 또는 전체)·page·size 를 client state 로 보유한다. 정렬은 서버 고정(name ASC).

  • 툴바: 공용 ListToolbar — 검색(라이브러리명, ≤100) + 타입 필터(AI/TRUST/전체) + 적용/초기화. read-only 라 생성 버튼 없음.
  • : 이름(font-medium) · 타입 배지(StatusPill — AI=info/TRUST=success, 운영사 PL 상세 #057 의 LIBRARY_TYPE_META 와 일관) · 음원 수(trackCount) · PL 사용 수(playlistCount, 본인 본사 PL 만 카운트) · 수정 시각(updatedAt, KST formatKstDateTime).
  • 빈 상태: 검색/필터 0건(ListNoResults CTA “라이브러리가 없습니다”) vs 진짜 빈 목록 구분.
  • 페이지네이션: 공용 ListPagination — 총 N개 + 이전/다음(경계 disabled).
  • read-only: 생성/편집/삭제·음원 할당 진입점 없음. 라이브러리명 셀은 상세 (/admin/libraries/[id], #107)로 진입하는 read-only Link 다(본사 매장 목록 #084 패턴 미러 — 행 자체 클릭이 아니라 이름 한 곳, data-testid="hq-library-link-{id}").

상세 (/admin/libraries/[id], SPEC #107)

apps/space/src/app/admin/libraries/[id]/page.tsx(server 셸) + hq-library-detail-client.tsx(client). 본사 PL 상세(#057)·운영사 라이브러리 상세(#053) idiom 을 정확히 미러하되 read-only — 음원 추가/ 제거·이름 수정·삭제·체크박스·일괄 제거 진입점이 모두 없다(편집은 운영사 apps/admin 전용).

구조(2 endpoint 분리, BE §D1): 메타는 server fetch(loadHqLibraryRefreshAwaregetHqLibrary), 트랙은 client query(useListHqLibraryMusic, 페이지네이션). 라이브러리당 음원이 카탈로그급으로 커질 수 있어 임베드 대신 서버 페이지네이션(운영사 #053 정확 미러).

  • 헤더: 뒤로가기(→/admin/libraries) + 라이브러리명 + 타입 배지(StatusPill — AI=info/ TRUST=success, HqLibraryDetailResponseType 기반) + subtitle 음원 N곡 · PL 사용 M개 · 조회 전용. 라이브러리는 단일 타입 묶음이라 타입 배지는 헤더 한 곳에만(트랙별 배지 없음, BE §D3).
  • 메타 카드(SummaryCard grid): 음원 수(trackCount) · PL 사용(playlistCount, hint “이 라이브러리를 사용 중인 본사 PL 수”) · 마지막 수정(formatKstDateTime(updatedAt)).
  • 트랙 테이블: useListHqLibraryMusic(libraryId, {page, size:20}). 컬럼 제목 · 길이 (formatTrackDuration — mm:ss, 1시간↑ h:mm:ss, 음수/NaN/undefined → ”—”) · 추가일 (formatKstDateTime(createdAt)). 정렬은 서버 고정(할당 시각 DESC). 로딩 (hq-library-music-list-loading) · 빈(hq-library-music-list-empty “담긴 음원이 없습니다.” CTA 없음) · 에러(hq-library-music-list-error) 분기. 공용 ListPagination(1-base 표기, testId 접두 hq-library-music-*).
  • 메타 fetch 실패(detail=null, 5xx/네트워크): 본문 전체를 danger Banner (hq-library-detail-error)로 대체 — 트랙 query 는 비활성(enabled:!!id, 불필요 호출 회피).

격리 (D2 — #080 §D3 일관)

라이브러리는 hqId 컬럼이 없는 운영사 공유 모델이다. verifyHqScope(403 가드 — ACTIVE· HQ_MANAGER·hqId 일치)만 통과하면 모든 활성 라이브러리 상세를 조회할 수 있다. PL 상세(#057)의 hqId != → 404 은닉 분기를 넣지 않는다. 활성 라이브러리 미존재/soft-deleted → 404 LIBRARY_NOT_FOUND → server 가 Next notFound() 로 분기.

사이드바

HQSidebar 의 “라이브러리”(/admin/libraries) 항목이 enabled(활성 라우팅) — #080 에서 disabled “준비 중”(이전 placeholder href /admin/library)에서 전환. SPEC 경로로 통일하면서 href 도 /admin/library/admin/libraries 로 정렬.

인가

GET /api/v1/hq/librarieshasRole("HQ_MANAGER") 1차 경계 + service verifyHqScope claim↔DB 재검증(#049/#057 재사용). 미인증 401 · 비활성/role·소속 불일치 403 PRINCIPAL_SCOPE_MISMATCH. 목록 fetch 는 generated apiFetch 가 BFF catch-all /api/backend/... 경유(토큰 서버 전용).

Followups

  • F1 라이브러리 상세 viewSPEC #107 — 메타 카드 + 소속 음원(트랙) 목록 read-only (/admin/libraries/[id]). 위 상세 섹션 참조.
  • F2 본사 PL → 라이브러리 추가 다이얼로그 — 운영사 PL edit 패턴 미러(권한은 본사 PL 에 한정).
  • F3 plan/type mismatch 게이트 — #060 F1 와 짝.