관리자 커스텀 종목 전용 페이지 — ID 8자리 눈대중에서 유저별 목록·이름+@핸들까지 (2026-09-02)
- 기간: 2026-09-02 (세션 1개,
3feaf604-f4c8-4a2d-a319-21b18878f081). 오너 지시: "커스텀 종목 조회 별도 페이지로 빼주고, 유저 별로 조회할 수 있게 해줘" → "유저표기는 이름 + @핸들로", "비활성종목은 왜 있는거야?" → go - 랜딩: PR #1148(Phase 1~2,
a423544f). 마이그레이션20260903020000_admin_search_users_handle_v1(함수 반환 열 추가). Production 반영은 릴리스 PR — 그 전까지 [스테이징]. 관리자 화면 자체는 docs 프로젝트/admin/으로 즉시 배포 - 설계서: 이슈 #1130 본문(예상 효과·개선사항 포함) + 결정 코멘트
- 정본:
DesktopAdminCustomExercises.tsx· 탭custom(desktopAdminTabs.ts) · 유저 대응표 로드(DesktopAdminFeature.tsxadminUserDirectory) ·admin_search_users_v1(handle 포함) - 도구: 없음(브라우저 실측은 워크트리 일회용 vite 하네스, 커밋 안 함)
- 게이트:
adminCustomExercisesPage.test.mjs4건 · 대리 인입 대상 검색 기대값(handle) · pgTAPadmin_wodup_import_on_behalf(함수 재생성 후에도 통과) - 버그리포트: 없음(기능 추가)
- 계약: 마커
adminCustomUserList·adminCustomExerciseList등록
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 1 | 새 탭 '커스텀 종목' — 소유 유저 목록 → 유저의 커스텀 종목 → 기존 상세 패널. 이름 @핸들 대응, 폴백, 비활성 기본 숨김+토글, 최근 등록순 | ✅ PR #1148 |
| Phase 2 | admin_search_users_v1에 handle 열 추가(drop 후 재생성) | ✅ PR #1148 · 릴리스 대기 |
| Phase 3 | 로컬 게이트 → CI 1회 → 랜딩 잠금 verify → 머지 | ✅ (a423544f) |
1. 배경
Production에 커스텀 종목 109건(활성 19, 소유자 4명)이 있는데, 관리자가 "유저 A가 만든 종목"을 보려면 전체 종목 표에서 종목 타입을 '유저 커스텀'으로 바꾼 뒤 상세를 하나씩 열어 '소유 9bca67e4…' ID 앞 8자리를 눈으로 대조해야 했다. 이름·이메일은 어디에도 없었다.
2. 문제 제기
유저 단위로 볼 화면이 없었다
전체 종목 표는 종목 단위 목록이라 소유자별 묶음·카운트가 없다.
소유자 이름을 볼 방법이 없었다
profiles 표는 RLS가 본인만 조회라 관리자도 못 읽는다. 관리자 전용 검색 함수는 이메일·표시 이름만 돌려주고 핸들이 없었다.
비활성 커스텀 종목의 정체가 불분명했다
비활성 90건 중 89건은 e2e 테스트 계정이 만든 CASE-005 리프트 … 종목을 08-24에 일괄 비활성 처리한 흔적, 1건은 다른 유저 종목 정리. 앱에는 유저의 카탈로그 삭제 기능이 없어 비활성 = 관리자 '종목 삭제'뿐이다.
3. 해결 방안
원칙 (오너 결정, 2026-09-02)
- D1 유저 표기 = 이름 + @핸들. 핸들은 서버 함수에 없어 반환 열을 추가(릴리스 전 폴백 = 이름만).
- D2 비활성 종목 = 기본 숨김 + '비활성 포함' 토글(테스트 흔적 89건이 목록을 덮지 않게).
접근
| 대안 | 판단 |
|---|---|
| 새 탭 3-pane(유저 → 종목 → 상세), 종목 행·상세 패널은 전체 종목 표 부품 재사용 | 채택 — 화면 코드 1파일, 편집·삭제는 기존 콜백 그대로 |
| 전체 종목 표에 '소유 유저' 필터 축 추가 | 기각 — 유저별 카운트·이름 대응이 표 한 축으로는 안 보인다 |
| 프로필 RLS를 관리자 열람으로 완화 | 기각 — 함수 반환 열 추가가 더 좁은 변경 |
4. 적용한 내용
Phase 1 — 화면 (#1148)
DesktopAdminCustomExercises.tsx: 커스텀(origin=user) 종목을 소유자별로 묶어 유저 목록(이름 @핸들·이메일·활성/전체, 종목 수 많은 순, 검색)·유저의 종목(최근 등록순createdAt, 비활성 토글, 검색)·AdmExerciseDetail. 이름 대응 실패 시 ID 앞 8자리 + 안내 한 줄.- 탭
custom(전체 종목 다음),DesktopAdmin라우팅·카탈로그 게이트 포함,DesktopAdminFeature가 탭 진입 시searchAdminImportTargets({ query: "", limit: 50 })1회. - 관리자 카탈로그 select에
created_at(어댑터·매퍼createdAt), 대리 인입 검색 행에handle.
Phase 2 — 서버 (#1148)
admin_search_users_v1drop 후 재생성: 반환 열handle, 검색어도 핸들 대조(앞@제거), 권한·주석 재적용. 기존 호출자 무영향.
주요 결정과 그 근거
- 유저 대응표를 화면이 아니라 피처가 들고 탭 진입 시 1회 읽는 이유: 화면은 props-in/콜백-out 계약, 실패 폴백도 화면 한 줄로 처리 가능.
작업 중 드러난 것
- 임시 마이그레이션 번호가 main 꼬리(다른 세션 #1142의
20260903010000)보다 앞이라migrations:renumber가20260903020000으로 옮김. 랜딩 절차대로 잠금 안에서 확정. - 멱등 검사는 함수도
create or replace를 요구한다(drop if exists 뒤여도). - 새 관리자 화면 파일은 TypeScript 경계 게이트의 "desktop presentation boundary" 목록에 등재해야 한다(형제 화면과 동일).
- 소스 앵커 게이트는 테스트의
indexOf("문자열")을 소스 앵커로 오인한다 — 렌더 결과 순서 비교는search(/…/)로.
5. 적용 결과
| 항목 | 결과 |
|---|---|
| 유저별 커스텀 종목 보기 | ID 8자리 눈대중 대조 → 유저 목록 클릭 1회 |
| 소유자 식별 | 없음 → 이름 @핸들(릴리스 후) · 이메일. 릴리스 전엔 이름만 |
| 기본 표시 종목 수(최대 소유자) | 90 → 1(활성만, 토글로 90) |
| 하네스 실측 | 유저 목록 박주희 @joohee 2/3·김비 1, 딥스(08-28)가 푸시업(08-20)보다 위, 토글 켜면 '테스트 흔적' 표시, 김비 클릭 시 'B의 종목' 1개 |
| 유닛 테스트 | 2,361 → 2,374 (전부 green) |
| Production 반영 | 릴리스 PR 머지 시 — [스테이징] |
6. 이번 개선으로 향상된 것
커스텀 종목 정리가 유저 단위 작업이 됐다
누가 무엇을 만들었는지 이름으로 보고, 그 자리에서 특성 편집·삭제까지 한다.
구조적으로 남는 것
- 관리자 화면의 유저 이름 대응 경로 1곳(
admin_search_users_v1, handle 포함). - 종목 행·상세 패널 부품 재사용 관례(새 관리자 목록 화면은 자체 구현 없이 조립).
남은 것
- 릴리스 PR 머지 → @핸들 표시·이슈 [반영완료].
- 사용자 50명 초과 시 소유자별 보강 검색(지금은 4명).