디자인 배송 v83 — 관리자 전체 종목 표 문법·필터 모달 + 종목 상세 동의어 카드 개편 (2026-09-01)
- 기간: 2026-09-01 (1세션, 오너 지시 "이거 디자인 반영좀 해줘")
- 랜딩: PR #1041(반입+배선,
c0c7d0cd) — 마이그레이션 없음, Vercel Production 배포. 후속 PR #1045(검색 동의어 표시, 이슈 #1044 — 아래 후속 절) - 설계서: 없음(배송 반입 건) — 분석·배선 판정 = 이슈 #1040 본문
- 정본:
src/react/contracts/designContract.ts마커 3종 등록/1종 해제 ·typescript-unused-gate.md래칫 기록(3개 파일 14건) - 도구: 없음
- 게이트: dead-css 고아 2규칙 삭제 · typescript-unused 베이스라인 3건(보존 계약 명시) ·
adminExerciseSynonymPanel단언 새 표면 이전 - 버그리포트: 없음(결함 수리 아님)
- 계약: 배송 계약 v2(manifest) — base
e62ce6a3, changed 4파일 상류 교집합 0 → verbatim 반입
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 1 | 반입 — verbatim 적용·범위 감사 | ✅ PR #1041 |
| Phase 2 | 배선 — 마커 등록·배송 Codex 후속 4건 판정 | ✅ PR #1041 |
| Phase 3 | 검증 — 로컬 게이트 전 체인·SSR 렌더 스모크·CI | ✅ (1차 CI RED 1건 수리) |
| Phase 4 | 랜딩 — 머지·Production 배포 확인 | ✅ c0c7d0cd |
| Phase 5 | 기록 — 본 문서 + 등록 2곳 | ✅ |
1. 배경
관리자 페이지의 전체 종목 화면은 필터가 목록 위에 세그먼트 3줄로 쌓여 있고, 목록 행은 분류·부위·기록 필드를 한 줄 텍스트로 뭉쳐 보여줬다. 종목 상세는 "종목 정보" 표가 동의어 카드와 내용이 중복이었고, 동의어 편집 어포던스(이름 수정·삭제·승격)가 행마다 텍스트 버튼으로 흩어져 있었다. 디자인 환경이 delivery-20260901(v83)로 이 두 화면을 재설계해 배송했다.
2. 문제 제기
- 전체 종목 목록에서 종목의 특성(타겟 부위 비중·기능 분류·필수 입력)을 보려면 행을 하나씩 눌러 상세로 들어가야 했다 — 목록이 비교 표 역할을 못 했다.
- 활성/비활성 필터는 기본 "활성만"이라 비활성 종목이 목록에서 아예 사라져, 관리자가 비활성 행의 존재를 잊기 쉬웠다.
- 동의어 관리 표면이 #963에서 기능 위주로 자란 상태(검색 규칙 설명문·검색 확인 상자·승격 버튼)라 시각적으로 무거웠다.
3. 해결 방안
원칙
- 배송 계약 v2: manifest base 확인 + 상류 교집합 실측 → 교집합 0이면 verbatim, 게이트(tsc·테스트·빌드)는 전량 유지.
- 배송이 렌더에서 뺀 표면의 계약(테스트 단언)은 삭제가 아니라 새 표면으로 이전.
- 배송이 "나중에 1줄로 부활"을 위해 남긴 코드(AdmSearchProbe 함수·승격 콜백 prop)는 지우지 않고 unused 베이스라인에 계약과 함께 등재.
접근 (배송 Codex 후속 4건 판정)
| 배송 요청 | 판정 |
|---|---|
| ① 종목 페이로드에 uuid(DB PK) 공급 | 추가 배선 불필요 — 관리자 페이로드의 mv.id가 이미 DB PK UUID 정본(adminExerciseCatalog가 canonical UUID를 강제). UI의 mv.uuid || mv.id 폴백이 정확한 값을 표시 |
| ② 전 종목 synonym 행에 id 공급 | 추가 배선 불필요 — Production 실측: 1,073종목 전부 대표 동의어 행 보유(id 없는 합성행 경로는 실 데이터에서 미발화) |
| ③ 마커 3종 등록·1종 해제 | 채택 — designContract에 adminAllExercisesFilterOpen·FilterModal·PartFilter 등록, StatusFilter 해제(렌더 소멸) |
| ④ 목록 페이로드에 targetMuscles·movementPattern·requiredInputs | 추가 배선 불필요 — AdminExercise → lgDesktopAdminProps가 이미 행 단위 공급 중 |
4. 적용한 내용
Phase 1 — 반입
DesktopAdminAllExercises.tsx(검색 최상단·필터 설정 모달·표 문법 목록·헤더 accent 추가 버튼) · DesktopAdminExercises.tsx(종목 정보 섹션 삭제·동의어 카드 액션 재배치·식별자 BRID/UUID 행·토스트) · DesktopWidgets.tsx(복사 글리프) · desktop-admin.css. base e62ce6a3 = origin/main 조상 확인, 교집합(base..main ∩ changedFiles 4) 0 실측 → verbatim. CHANGE-NOTES.md 디자이너 누적 로그 채택.
Phase 2 — 배선·부수 수리
- designContract 마커 3종 등록·1종 해제(위 표 ③).
- dead-css 게이트 적발 2건 수리: 배송이 TSX에서 지운 블록의 잔존 CSS(
.adm-grp-inline·.adm-syn-rule) 삭제 — 배송 자체의 "죽은 규칙 삭제" 원칙과 동일. - 고아 import 2건(AdmStatus·ORIGIN_SHORT) 정리, unused 베이스라인 3건(AdmSearchProbe 함수 보존·onPromoteAlias prop 보존·exerciseFields options 파라미터) + 게이트 문서 래칫 기록 동조.
adminExerciseSynonymPanel테스트: 렌더에서 사라진 검색 규칙 설명문·검색 확인 상자 단언을 "부재 확인"으로 이전.
작업 중 드러난 것
- unused 베이스라인을 갱신하면 게이트 문서(
docs/gates/typescript-unused-gate.md)의 "N개 파일 M건" 래칫 기록도 같은 커밋에서 동조해야 한다 —typescriptUnusedGate.test.mjs가 문서↔베이스라인 일치를 단언(1차 CI RED의 원인. 로컬 전체 테스트를 베이스라인 갱신 전에 돌려 놓쳤다 — 베이스라인·게이트 산출물을 만진 뒤에는 해당 동조 테스트 재실행). - PowerShell 리다이렉트로 만든 baseline JSON은 BOM이 붙어 게이트 파서가 죽는다 — 산출물 파일은 node로 직접 쓴다.
--print-baseline재생성은 파일 전체를 덮으므로, diff에서 이번 변경분 외 항목이 없는지 확인 후 채택.
5. 적용 결과
| 항목 | 전 → 후 |
|---|---|
| 전체 종목 목록 | 이름+한줄 메타 → 표(분류·타겟 부위 % 칩·기능 분류·필수 입력 4열, 헤더 행) — 목록에서 바로 비교 가능 |
| 필터 | 목록 위 세그먼트 3줄 → "필터 설정" 모달(3행+파셋 카운트+활성 수 배지), 활성/비활성 필터 삭제(비활성 행도 취소선으로 상시 노출) |
| 종목 상세 | 종목 정보 표 삭제(헤더·브레드크럼이 커버), 동의어 = 카드 우상단 액션(대표로/연필/×)·페리윙클 별칭 칩·추가/삭제 토스트, 식별자 = 상태/출처/BRID(복사)/UUID(복사) |
| 검증 | 로컬 게이트 전 체인 green · 유닛 2,271(1차 CI 1건 RED → 문서 동조 수리 후 verify pass 4m8s) · vite build 앱+admin · SSR 렌더 스모크(% 칩·필터 훅·비활성 노출·StatusFilter 소멸) |
| 시각 품질 | 간격·정렬 등은 디자이너 하네스 실측(배송 문서 기록)에 의존 — 사람 눈 판단 영역 |
6. 이번 개선으로 향상된 것
전체 종목 화면이 관리 도구다워졌다
1,000종이 넘는 카탈로그를 표로 훑으며 타겟 부위 비중·필수 입력을 행 단위로 비교하고, 타겟 부위 축 필터가 새로 생겼다. 비활성 종목이 기본 노출로 바뀌어 "안 보여서 잊는" 관리 공백이 사라졌다.
배송 후속의 실측 판정 관행
배송이 요청한 배선 4건 중 3건이 "이미 충족"으로 판정됐다 — 페이로드 정본 코드 확인 + Production 실측(동의어 0건 종목 카운트)이 근거. 요청 문면대로 중복 배선을 넣지 않았다.
구조적으로 남는 것
designContract 마커 3종 · unused 게이트 래칫 기록의 "보존 계약" 관행(부활 1줄 복원 조건 명시) · 베이스라인↔문서 동조 함정 기록.
후속 — 동의어 이름을 대표 이름과 동등한 검색 결과 행으로 (같은 날, 이슈 #1044 · PR #1045→#1047)
오너 지시 "검색 결과에는 모든 synonym을 다 표시" → 1차(#1045)는 행 이름 아래 동의어 나열로 반영했으나 오너 정정: "'오버헤드 프레스'·'숄더 프레스'·'밀리터리 프레스'가 각각 동등한 검색결과로 표시되게, 하지만 클릭했을 때 나오는 건 동일하도록". 2차(#1047)로 대체 — 검색 중에는 매칭된 표기(synonym)마다 자기 이름의 행이 서고(동의어 행에는 대표 · ○○ 안내줄), 같은 종목의 어느 행을 눌러도 우측 상세는 동일(같은 종목 행 동시 하이라이트). 별칭 매칭은 소속 표기의 행으로, 종목 단위 필드(분류·부위·계열·BRID)만 걸리면 대표 행 하나. 검색어 없으면 종목당 대표 행 하나, 헤더 카운트는 종목 수. 1차에서 잡은 매칭 공백(비대표 표기의 별칭이 검색에서 누락 — '밀프스' 0건)의 수리는 유지. 브라우저 하네스 실측(이름 행 전개·동의어 행 클릭 동일 상세·별칭→소속 표기 행·검색어 삭제 원상), 게이트 전 체인 green, Production 청크 마커 실측.
추가 지시 3건(#1050): Production 화면 확인 후 오너 지시 — ① 동의어 행의 대표 · ○○ 안내줄 삭제 ② 같은 종목 행 동시 하이라이트 → 클릭한 행 하나만 선택 표시(선택 행이 검색 변화로 사라지면 같은 종목 대표 행이 이어받음) ③ 이름·별칭 편집 후 화면 유지 — 모든 관리자 편집이 저장 후 카탈로그를 다시 불러오는 동안 준비 플래그가 false로 떨어져 화면이 '불러오는 중' 게이트로 교체 → 목록 언마운트로 선택 종목·검색어 초기화되던 것을, 한 번 로드된 뒤에는 게이트 재진입 없이 이전 목록을 유지한 채 제자리 갱신으로 수리(fail-closed는 첫 로드·오류에만, 소스 앵커 테스트 이전). ③은 로그인 필요한 실제 편집 경로라 하네스 재현 불가 — 원인 코드 특정+게이트·전체 테스트로 검증.
추가 지시 ④·⑤(#1054, 오너 지시로 CI 대기 없이 admin 머지): ④ 삭제 팝업 통일 — 확인 팝업 후 서버 거부 시 브라우저 기본 알림창이 한 번 더 뜨던 이중 팝업 제거(오류·거부 안내는 오류 토스트로, adminUnavailable의 window.alert 폐기). 특히 새로 노출된 대표 카드 ×의 거부는 번역이 없어 무의미한 일반 문구였던 것을 사유 번역(LG_SYNONYM_DEFAULT — "대표 표기는 삭제할 수 없습니다…")으로 교체. ⑤ 검색 정규화 — 전체 종목 검색을 제품 종목 검색 엔진(filterExerciseSynonymRows) 호출로 교체: 접기(NFKC·소문자·공백/기호 제거)+한글 자모 분해(IME 조합 중간 매칭)+어순 자유 토큰+별칭 앞부분 일치·표기 매칭 시 별칭 행 숨김. 띄어쓰기에 따라 결과가 달라지던 문제 해소('숄더 프레스'→'바벨 숄더프레스' HIT 실측). 관리자 보조 필드(계열·BRID·분류·부위/기능)는 같은 접기의 폴백 매칭으로 유지. 계약 테스트 이전 3건(오류 토스트 톤 save→error·대표 삭제 번역 케이스).
추가 지시 ⑥(#1056): 첫 글자 입력 버벅임 — 목록이 1,073행을 전부 마운트해 키 입력마다 전체 재렌더되던 것을 공용 창 렌더 훅(useListWindow, 피커 BUG-021 수리와 동일 관례) 30행 창으로 교체. 스크롤이 바닥 근처에 오면 30행씩 확장, 검색어 변경 시 창 리셋, 종목 수 카운트는 전체 기준 유지. shown을 useMemo로(창 items 정체성 계약). 하네스 실측(30행 마운트·스크롤 확장 30→60·검색 시 리셋)+SSR 회귀 테스트(120종목→30행) 추가. 함정 기록: 숨겨진 브라우저 팬에서는 IntersectionObserver가 발화하지 않는다 — 스크롤 확장류 실측은 팬을 전면에 띄우고 할 것.
추가 지시 ⑦(#1058): 로딩 중 반토막 배경 — 브랜드 스플래시(.lg-splash)는 본래 순흑인데 데스크톱 호스트 규칙(.gym-app-platform-gate > .lg-shell-scope { background: transparent })이 스플래시까지 투명으로 덮어써, 그 밑의 좌백/우새벽 분할 레터박스가 로딩 중 그대로 보였다. 수리 = 스플래시에만 예외 규칙(fixed·inset 0·순흑, zoom 무관 뷰포트 전체) — 준비되어 실제 화면이 그려질 때 함께 사라진다. 로그인·안내 게이트 불변. 하네스 실측(zoom 0.7 클램프에서도 전체 순흑·이음새 없음).
추가 지시 ⑧(#1062 → 오너 지시로 #1064 전체 롤백): 캔버스 바깥 레터박스를 순흑으로 반영했으나, 배포 화면에서 검은 여백이 앱 옆의 어색한 띠로 보였고 오너가 실제로 지목한 대상은 앱 안쪽 지면(메뉴·카드 사이 글로우 여백)이었다가 그마저 "그냥 원래대로" 지시 — #1062를 통째로 원복(그라디언트 레터박스 복귀), 지면 검정 시도분은 랜딩 전 폐기. 로딩 스플래시 전체 순흑(⑦ #1058)은 유지. 교훈: 배경·여백처럼 가리키는 대상이 여럿인 시각 요청은 반영 전에 캡처로 대상 합의부터. 함정 재확인: 테스트 파일 문자열 치환에 PowerShell Set-Content 금지(전 파일 줄바꿈 오염) — Edit 툴로.
남은 것
- 종목 특성 수정 경로: 상단 연필(수정 폼 진입) 제거로 현재 UI에서 종목 특성(분류·단위·계수 등)을 편집할 진입점이 없다 — 배송 노트 명시 "후속 인라인화 대기"(AdmInlineEditor 코드 보존). 디자인 측 후속 배송 대상.
- 동의어 없는 종목의 합성행 편집: Production 현재 0건이라 미발화지만, 이후 id 없는 행이 생기는 경로가 생기면 편집 어포던스가 그 행에서만 비노출된다(게이트 = synonym.id).