Skip to content

반복수 종목 등급이 종목 상세까지 오게 — 풀업 상세 빈 등급 칸에서 성별 설정까지 (2026-09-02)

  • 기간: 2026-09-02 (세션 1개 — 오너 보고 "우리쪽 풀업에서 지금 등급 데이터가 안나가는거 같은데 확인좀 해줄래?" → 조사 → 오너 go "이슈 만들고 진행해줘. 그리고 성별도 추가해줘")
  • 랜딩: PR #1145(Phase 1·2, f627ea84) — 마이그레이션 20260903010000_profile_sex_v1.sql 1건, 엣지 0건. Production 적용 = 릴리스 v0.13.0(PR #1133, e48e7035, 2026-09-02 — 태그 v0.13.0)
  • 설계서: 없음(수리 건 — 분석·Phase 계획은 이슈 #1142 본문, "예상 효과·개선사항" 절 포함)
  • 정본: 등급 전달 = barbelicMappers.ts compactPrItemFromParts(지표 무관 rank·cutlines·gender) · 화면 단위 분기 = rank.unit(모바일 RecordsDetail.tsx) / DkRank.unit(데스크톱 DesktopPrDetailParts.tsx) · 성별 저장 = set_profile_sex_v1(text) → 저장소 setProfileSexprofileController.saveProfileSex → 프로필 props sex
  • 도구: Production 실측은 Management API 쿼리 러너(read_only), 재현은 실제 매퍼에 오너 데이터 모양을 넣은 스크립트(scratchpad)
  • 게이트: tests/react/repsStrengthRating.test.mjs(5) · tests/react/profileSex.test.mjs(4) · supabase/tests/database/profile_sex_v1.test.sql(pgTAP 11) · backgroundTokens.test.mjs·controllerBoundaries.test.mjs 앵커 갱신(pending-changes 신고) · 디자인 계약 마커 mobileProfileSex/profileSex
  • 버그리포트: bug-report/bug-061-20260902.md
  • 계약: profile-screen-props.md onSaveSex 행 · desktop-screens-props.md 성별 절

Phase 현황

Phase내용상태
Phase 1반복수 등급을 종목 상세까지 전달("중량만" 게이트 폐지, 등급표 단위 '회')✅ PR #1145 (f627ea84)
Phase 2프로필 성별 설정(서버 함수 + 내 정보 화면 + 저장 뒤 기준표 재적재)✅ PR #1145 (f627ea84)
Phase 3랜딩·기록·릴리스✅ 랜딩(#1145) · 기록(#1146) · 릴리스 v0.13.0(#1133)

1. 배경

Strength Level 기준표는 2026-08-24 전종목 반입(#631) 뒤 반복수 지표(풀업·딥스·푸시업 등 61종)까지 서버에 들어와 있었고, 등급 계산 코드도 반복수 행을 처리했다. 기록 탭 즐겨찾기 표에서는 풀업 "마스터 · 상위 5%"가 보였다. 오너가 종목 상세에서 등급이 비어 있는 것을 보고 2026-09-02 보고했다.

2. 문제 제기

종목 상세용 등급 칸이 "중량 기준 등급만" 채워졌다

조립 코드(compactPrItemFromParts)는 등급 계산 결과(strengthStandard)를 지표와 무관하게 만들면서, 상세 화면이 읽는 칸(rank·cutlines·gender)은 metric === "weight"일 때만 채웠다. 이 규칙은 #221(2026-07-15, 핵심 8종목 남성 중량 기준)에서 생겨 전종목 반입 뒤에도 남았다. 즐겨찾기 표는 strengthStandard.rank로 되돌아가는 대체 경로가 있어 거기서만 보였다. 재현: 오너 데이터 모양(풀업 30회 + 남성 반복수 기준표)으로 실제 매퍼를 돌리면 계산 결과 rank = 마스터 5%, 상세용 rank = null, cutlines 0개.

등급표 팝업이 1RM·kg만 전제했다

모바일 등급표 팝업은 현재 행을 current1RM.value와 kg 컷라인으로 판정하고 단위를 kg로 고정했다. 데스크톱 상세도 단위 kg 고정.

성별을 온보딩 뒤에 바꿀 길이 없었다

성별은 온보딩 완료 함수(complete_onboarding_v2, 키·체중 필수)만 썼다. get_pr_overview는 프로필 성별이 없으면 남성 기준표만 실어 보낸다. 오너 프로필은 성별이 비어 있어 풀업 30회가 남성 기준(엘리트 30회)으로 판정됐다(여성 기준 엘리트 19회).

3. 해결 방안

원칙 (오너 지시, 2026-09-02)

  • D1 "go, 이슈 만들고 진행해줘" — 분석·Phase 계획(#1142)대로 착수.
  • D2 "성별도 추가해줘" — 프로필에서 성별을 설정·변경할 수 있게(온보딩 전용이던 값).

접근

대안판단
화면마다 strengthStandard.rank로 되돌아가는 대체 경로 추가기각 — 화면 2곳에 같은 우회, 원인 그대로
조립 코드의 "중량만" 게이트 제거, 화면은 단위 분기만채택 — 한 경로
성별을 온보딩 완료 함수로 재저장기각 — 체중 기록 없으면 불가, 온보딩 재완료 부작용
set_profile_sex_v1(p_sex) 신설(아이디 저장 함수와 같은 형태)채택 — 본인 sex만, m/f, 22023
성별 저장 뒤 등급 즉시 반영loadPrDashboard(force)로 PR 화면 데이터 재적재(서버가 프로필 성별의 기준표만 보내므로 필수)

4. 적용한 내용

Phase 1 — 반복수 등급 전달 (#1145)

  • compactPrItemFromParts: displayedStrengthRating(중량만) 제거 → strengthRating 그대로 rank·gender·weightClass·cutlines·cutlinesOpen.
  • RecordsDetail.tsx: 등급표 현재 행 = d.rank.tier(1RM 재계산 폐기), 단위 = d.rank.unit === "reps" ? "회" : "kg". DesktopPrDetailParts.tsx 동일, DkRank.unit 추가.
  • 테스트 repsStrengthRating.test.mjs: 상세 전달·경량 경로 동등·표와 상세 일치·성별 분기·0회 등급 없음·중량 회귀 없음.

Phase 2 — 성별 설정 (#1145, 20260903010000)

  • 서버 set_profile_sex_v1(text): 미로그인 42501, m/f(male/female·대소문자·공백 정규화) 외 22023, ensure_user_profile() 뒤 본인 행만 갱신, {user_id, sex} 반환. postcheck(존재·authenticated 실행 권한). pgTAP 11단언.
  • 앱: 저장소 setProfileSex(형식은 RPC 전에 거름, LG_PROFILE_SEX_INVALID) → 소셜 도메인/API → 컨트롤러 saveProfileSex(소유자 가드로 원격 프로필 갱신·토스트·reloadStrengthStandards 콜백) → 조립 루트가 콜백에 loadPrDashboard({screen:"pr", force:true}) 연결 → 내 정보 화면 성별 행(모바일 .ob-pun 토글, 데스크톱 dk-btn 두 개). 프로필 props에 sex.
  • 계약 문서·디자인 마커 등록, 경계 테스트 가드 수 3→4.

주요 결정과 그 근거

  • 등급표 현재 행을 화면이 다시 계산하지 않고 매퍼 등급(rank.tier)을 쓴다 — 중량/반복수 분기를 화면에서 없애고 데스크톱과 같은 방식으로.
  • 성별 저장은 선택 즉시(별도 저장 버튼 없음) — 값이 둘뿐이고 되돌리기가 한 탭이라.

작업 중 드러난 것

  • profileProvisioning.test.mjs가 schema.sql에 오너 실명이 들어가는 것을 막는다 — 마이그레이션 주석의 유저 예시는 "유저 A"로.
  • 디자인 계약(designContract.ts)에 data-lg-hook 마커를 등록하지 않으면 designContract.test.mjs가 막는다.
  • controllerBoundaries.test.mjs가 프로필 컨트롤러의 소유자 가드 발생 수를 센다 — 프로필 갱신 액션을 추가하면 기대값 동반 갱신.
  • 브랜치 작업 중 다른 세션이 main에 마이그레이션(20260902236000)을 랜딩 — migrations:renumber가 "tree is behind"로 거부, 커밋 → 리베이스 뒤 재실행.

5. 적용 결과

항목결과
풀업 상세 등급(오너 데이터 모양, 단위 테스트)없음 → 마스터 상위 5% (rank·cutlines 9칸·성별 라벨)
즐겨찾기 표 vs 종목 상세 등급표만 표시 → 둘 다 같은 등급(테스트 고정)
등급표 팝업 단위(반복수 종목)kg → 회
성별 설정 경로없음 → 내 정보 1탭(모바일·데스크톱), 저장 뒤 기준표 재적재
로컬 게이트npm run check + unused 통과
CI1회 통과(pgTAP 포함) — run 33615367787
Production릴리스 v0.13.0 적용 완료(2026-09-02): DB 꼬리 20260903010000·set_profile_sex_v1 존재 실측, 실서비스 번들 app-BaC3Pk_d.js, smoke 초록. 오너 프로필 성별은 오너가 앱에서 직접 설정(값을 대신 정하지 않음)
릴리스 레인frontend 잡의 "실서비스 도메인 = 이 배포본" 검사가 배포 직후 반영 지연으로 1회 빨간불(smoke·태그 건너뜀) → 실패 잡 재실행으로 완주. 도메인은 재실행 전에 이미 새 번들을 제공하고 있었다
미검증(시각)등급표 팝업 '회' 표기·성별 토글의 시각 품질

6. 이번 개선으로 향상된 것

맨몸 종목 기록자에게 등급이 보인다

풀업·딥스·푸시업 등 61종의 종목 상세에 등급 배지·상위 %·등급표가 나온다. 즐겨찾기 표와 상세가 같은 값을 보여준다.

성별을 나중에 설정·변경할 수 있다

온보딩 때 성별을 안 넣었거나 바꿔야 하는 사용자가 내 정보에서 1탭으로 바꾸면 등급 기준표가 즉시 바뀐다.

구조적으로 남는 것

  • 등급 전달은 지표 무관 한 경로(테스트 고정, 옛 게이트 이름 부재 단언).
  • set_profile_sex_v1 + pgTAP, 프로필 props sex, 디자인 마커 2개, 계약 문서 2곳.

남은 것

  • 없음 — 릴리스 v0.13.0 적용, 이슈 #1142 [반영완료] 종결. 같은 릴리스의 #1140·#922·#1102·#1103도 함께 종결.
  • 릴리스 레인 후속(범위 밖): frontend 도메인 대조 검사가 배포 직후 반영 지연에 민감하다 — 재시도(예: 10초 간격 3회)를 두면 헛빨간불이 준다.
  • 성별 미설정 기존 계정에 대한 별도 안내 팝업은 두지 않았다(내 정보 행 문구로만).