디자인 배송 v85 — 종목 상세 대표 기록 지표 표현 · 세션 상세 수행 강도 칼럼 · 종료→저장 단일 카드 · 내 정보 편집 행 · 그룹 보드 직행 (2026-09-03)
- 기간: 2026-09-03 (세션 1개, 오너 지시 원문 "이거 디자인 반영좀 부탁할게" + 배송 zip
바벨릭 (4).zip=delivery-20260903, base5e5bca44) - 랜딩: PR #1167 (Phase 1~4,
ede02361) · PR #1169 (Phase 6,f8fec25e) — 마이그레이션 없음, Vercel 배포 O - 설계서: 없음(디자인 배송 — 정본은 zip 안
PR-DRAFT.md·src/react/ui/CHANGE-NOTES.md). 분석·Phase 계획 = 이슈 #1166 본문 - 정본:
src/react/ui/mobile/lib/recordMetric.ts(종목 상세 표기 규칙 — 매퍼 kind 입력 어댑터) ·src/react/ui/mobile/lib/IntensityGuide.tsx(세트 강도 가이드 모달) ·docs/contracts/records-props.md§대표 기록 지표 ·docs/contracts/group-props.md(onOpenBoard) ·docs/contracts/profile-screen-props.md(onChangePhoto·onSaveName) - 도구: 반입 감사 = 3-way 시험 병합(
git merge-file) + CSS 헝크 감사 서브에이전트 1회 + 로그인 없는 브라우저 하네스(워크트리 루트 일회용, 커밋 제외) - 게이트:
designContract(마커 13건) ·check:dead-css(동적 클래스in-0…in-10·in-over) ·check-eslint억제 베이스라인 ·recordMetricPhase1/3·compositeSessionDetailDisplay·workoutFinishAutoSave·groupScreens·profileHandle·controllerBoundaries(프로필 가드 5) · e2eerror-cases/CASE-028(재저장 경로) - 버그리포트:
bug-066-20260903.md— 복귀 바 뒤 추가 세트 강제 종료 소실(스테이징에서 발견, Production 미배포). 되돌림 4건은 반입 감사에서 차단 - 계약:
records-props.md§대표 기록 지표 = main 데이터 계약(recordMetric.kind·currentMaxReps/currentMaxHold/cardioLongest·cardioByDistance·rank.unit)로 개정 ·group-props.mdonOpenBoard?(id)신설, 보드 헤더 "오늘" 칩 ·profile-screen-props.mdonChangePhoto?(file)·onSaveName?(name)신설
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 0 | 반입 감사 — 3-way 충돌 19곳·되돌림 4건·마커 미등록 9건·종목 상세 이중 구현 발견, 오너 결정 D1~D3 | ✅ 이슈 #1166 본문 |
| Phase 1 | 3-way 반입 + 되돌림 복원 + 마커 등록 + 테스트 재조준 5파일 | ✅ PR #1167 (56489354) |
| Phase 2 | 종목 상세 디자인을 main 데이터 계약 위에 이식(어댑터) | ✅ PR #1167 (5252c10c) |
| Phase 3 | 컨테이너 배선(보드 직행·사진·이름) + e2e CASE-028 | ✅ PR #1167 (fa0e0a5d) |
| Phase 4 | 브라우저 하네스 실렌더 9화면 | ✅ |
| Phase 5 | PR 1개·CI 1회·머지 → [스테이징] + 이 기록 | ✅ PR #1167 (ede02361) |
| Phase 6 | "한 세트만 더할게요" 복귀 뒤 초안 저장 재개 — 추가 세트 강제 종료 소실 수리(오너 표면 점검 질문에서 발견) | ✅ PR #1169 (f8fec25e) |
1. 배경
디자인 환경(Claude Design)이 9/2~9/3에 모바일 다섯 화면을 손본 배송 delivery-20260903을 냈다. 종목 상세(대표 기록 지표 — 풀업·플랭크·러닝처럼 1RM이 없는 종목의 최고 기록 표현), 세션 상세(세트마다 수행 강도 칼럼과 가이드 팝업), 운동 종료 흐름(확인 카드 하나가 저장까지 보여줌, 완료 화면 복귀 바), 내 정보(사진·이름·아이디 변경 행), 그룹(목록에서 보드 직행).
배송 base(5e5bca44) 이후 main은 47커밋 전진해 있었고, 특히 같은 주제를 main이 병행 구현하고 있었다: 이슈 #1100·#1102·#1103·#1110·#1142가 서버 → 매퍼 → 종목 상세까지 대표 기록 지표를 이미 연결해 둔 상태였다.
2. 문제 제기
2-1. 종목 상세는 같은 기능이 두 곳에서 따로 만들어져 있었다
유저 A가 풀업 상세를 열면, main은 매퍼가 내려준 recordMetric.kind와 currentMaxReps로 "최고 반복수 12회"를 그린다. 배송본은 서버가 best·bestTable·fields를 준다는 전제로 화면을 다시 짰다. 배송본을 그대로 넣으면 그 필드가 없어 풀업·플랭크·러닝 상세의 최고 기록·그래프·PR 목록이 전부 "—"로 비고, 오너가 이미 정한 규칙(무게+시간은 무게별 표 보류, 유산소는 최장 거리가 대표)과도 어긋나며, 테스트 4파일이 깨진다. 배송 PR 초안도 "병합 시 판단 필요"라고 적어 놓았다.
2-2. 이미 랜딩된 수리 4건이 되돌아가 있었다
전부 PR #1049(#928 정리 묶음, base의 조상)가 넣은 코드: 드래그로 세트를 추가하다 브라우저가 포인터를 회수하면 리스너를 정리하는 처리, 그룹 목록 불러오기 실패 시 "다시 시도" 화면(코드+CSS), 달력 월을 넘길 때 그 달의 보드 표시를 불러오는 콜백. v84 반입과 같은 유형 — 디자인 환경이 #1049 이전 본문 위에 새 작업을 얹었다.
증거: git log -S"pointercancel" -- WorkoutRecord.tsx 등 → 도입 커밋 7fd716e6, git merge-base --is-ancestor 7fd716e6 5e5bca44 참.
2-3. 계약 마커 미등록 13건과 사라진 마커 1건
배송이 새로 붙인 data-lg-* 이름(복귀 바·강도 가이드·프로필 편집 카드·이름/아이디 모달·누적 모달 등)이 장부(designContract.ts)에 없었고, main의 prDetailCardioTable(테스트 단언 있음)은 배송이 뺐다.
2-4. 컨테이너 배선이 없는 콜백 3건
"운동하러 가기"(onOpenBoard), "프로필 사진 변경하기"(onChangePhoto), "이름 변경하기"(onSaveName)는 화면만 오고 컨테이너 쪽 동작이 없었다. 이름은 서버에 저장 경로 자체가 없었다(온보딩 완료 RPC만 표시명을 씀).
3. 해결 방안
원칙 (오너 결정 D1~D3, 2026-09-03)
- D1 "D1은 권고. 디자인은 모양만 맞추면 돼" — 종목 상세 데이터 계약은 main(서버 연결 완료분) 유지, 디자인은 표현만 이식. 기각 = 배송 계약대로 서버·매퍼 재작업.
- D2 무게+시간 종목(파머스 홀드 등)은 "가장 긴 버티기 + 그때 무게 병기", 무게별 표는 보류. 기각 = 배송의 무게별 표 모달.
- D3 유산소 히어로 큰 숫자는 최장 거리, 거리별 최단 시간은 3열 표 팝업. 기각 = 최근 갱신된 기준 거리의 최단 시간을 대표로.
접근
| 대안 | 판단 |
|---|---|
| 배송 파일 통째 복사 | 기각 — main 전진분 10파일 소실(#1150 wfx-pctnote 1줄까지), 되돌림 4건 재발 |
3-way 병합(git merge-file) + 충돌 6곳 수동 해소 + 되돌림 main 복원 | 채택 (Phase 1) |
| 종목 상세: 배송 계약으로 서버·매퍼 재작업 | 기각(D1) — #1100~#1103 되돌림, 규모 큼 |
종목 상세: 배송 recordMetric.ts를 main kind 입력 어댑터로 축소하고 화면은 main 데이터 필드 유지 | 채택 (Phase 2) |
| 거리별 표를 화면 안 인라인 표로 유지(main 방식) | 기각 — 디자인은 히어로 탭 → 표 모달. 표 부분을 RcDistanceTable로 분리해 테스트는 컴포넌트 직접 렌더 |
4. 적용한 내용
Phase 1 — 3-way 반입 + 되돌림 복원 + 마커 등록 (56489354)
- 충돌 없는 10파일 배송 그대로, 충돌 6곳(SessionSheetBody 3·GroupScreen 2·ProfileScreen 1)은 main 전진분(#1151 복합 세트 한 줄 표기
formatCompositeSetLine·복합 반복수ssReps, #922 신고 콜백onReportComment, #1142 성별 설정 행) 보존 위에 디자인 델타 적용. - 되돌림 4건 main 기준 복원(2-2).
GroupScreen의onOpenInfo는 보드 화면이 더 이상 읽지 않아 계약 문서에 명시하고 제거(미사용 게이트). designContract.ts마커 11건(+Phase 2에서 2건),dead-css-baseline.json동적 클래스in-0…in-10·in-over(런타임 조합),eslint-baseline.json억제 주석 문구 변경 동조.- 테스트 재조준 5파일: 세션 상세 합계 문구("총 무게 · 메인세트 n세트 (m회)", 스탯박스 "(n회)"), 라운지 라벨 삭제, 아이디 변경 행, 완료 화면 복귀 바 부활(구 "폐기" 단언 반전), 세트 값 추출 정규식에 강도 버튼 추가. 계약 단언(복합 횟수 합산·존 집계 서버 전용)은 유지.
Phase 2 — 종목 상세 이식 (5252c10c)
lib/recordMetric.ts=rcMetric(kind):one_rm | max_reps | max_hold | distance_time | none→ 태그(1RM/MAX/HOLD)·값 분해·개선분·누적 라벨·세트 토큰·그래프 눈금·빈 상태 문구. 계산·판정 없음.RecordsDetail.tsx: 종목명 아래 등급 행rc-rank(등급 단위는rank.unit), 최고 기록 아래 "YYYY.MM.DD 기록", 누적 숫자 탭 → 연도별 누적 모달(지표/세트수/반복수/훈련일수,yearStatsByYear그대로), PR 행 태그·개선분, 훈련 기록 세트 토큰(12회 x2·1:30 x3·40kg 1:00 x1·5km 24:31 x1), 거리별 3열 표 모달RcDistanceTable(export, hookprDetailCardioTable), 그래프는m기반(마지막 값 라벨 "현재 1RM 180kg" / "최고 12회").- 유지: 1RM 경로 표기·계산, 측정 1RM 계약 문구(
measuredPrFrontendContract), 연도 목록availableYears(#1110), RM 모달. records-props.md§대표 기록 지표를 main 계약 기준으로 다시 씀(배송본 절은 서버가 주지 않는 필드를 적고 있었다).
Phase 3 — 컨테이너 배선 + e2e (fa0e0a5d)
groupStore.openGroupBoard(groupId): 목록에서 보드로 직행(라운지 데이터 동시 적재 → 뒤로가기가 빈 라운지에 떨어지지 않음).- 이름 변경:
barbelicRepository.updateProfileDisplayName(profiles.display_name 직접 갱신, RLSprofiles_update_own, 온보딩과 같은 공백 정리·1~24자) →authProfileDomain→barbelicApi→profileController.saveProfileName(remoteProfile 갱신·토스트) →mobileApponSaveName. 사진 변경은 기존uploadProfilePhoto액션 연결. - e2e
error-cases/CASE-028-finish-back-one-more-set-resave-round-trip: 종료 카드 저장(개정 1) → "한 세트만 더할게요" → 세트 추가 → 재저장(개정 2, 기존 줄 id 유지, 세트 3줄).externalAuthE2eConfig목록 등재. controllerBoundaries프로필 가드 수 4 → 5(이름 저장 추가).
Phase 6 — 복귀 뒤 초안 저장 재개 (#1169)
- 오너가 "에러가 될만한 표면은 없을까?"를 물어 점검하다 발견: 즉시 저장이 끝나면 초안 스냅샷 발행이 멈추는데(BUG-044 방지) 복귀 바로 돌아가도 풀리지 않아, 세트를 더 적다가 앱이 꺼지면 사라졌다.
- 수리 =
mobileReopenedDraftSnapshot: 복귀 뒤 발행되는 초안에 저장된 기록의editSessionId·expectedRevision과exercisesDirty: true를 싣는다. 복원되면 기존 "완료 기록 수정" 경로(즉시 저장 없음·수정 저장·OCC)로 열려 기록이 두 개 생기지 않는다. 대안(멈춤만 해제 → 기록 중복 / 컨테이너 편집기 의도 전환 → 편집기 재마운트)은 기각. - 테스트
workoutFinishBackResumeDraft신설 + 앵커 2파일 재조준. 상세 = BUG-066.
주요 결정과 그 근거
- 배송의
metricKindOf(fields)판정을 버리고 매퍼 kind를 받는 이유: 판정 정본은src/react/recordMetric.ts·record-metric.md한 곳이어야 하고(원칙 A), 화면이 다시 판정하면 두 규칙이 갈린다. - 거리별 표를 모달로 옮기면서 테스트는
RcDistanceTable직접 렌더로: 정적 렌더는 탭 뒤 상태를 열 수 없다. 화면 단언은 "인라인 표 없음 + 히어로 탭 가능"으로. records.css는 Phase 1 커밋에서 뺐다가 Phase 2에 넣었다: dead-css 게이트가 Phase 1 시점엔 미사용 클래스 5개를 잡기 때문(Phase 단위 게이트 원칙).
작업 중 드러난 것
- 디자인 환경이 또 #1049 이전 본문 위에 작업했다(v84와 동일 PR). 배송 base가 #1049 뒤여도 파일 본문은 그 이전일 수 있다 —
git log -S로 도입 커밋을 특정하는 헝크 감사가 이번에도 4건을 잡았다. - 배송 zip이 "완료 화면 진입 토스트 제거"를 후속으로 적었지만 실측상 그 토스트는 이미 없었다. 배송의 Codex 후속은 실측 판정부터(v83·v84 교훈 재확인).
workout.css는 main이 #1150 안내 문구 규칙 1줄을 더해 두어 통째 교체하면 사라진다 — 3-way 필수.- 로컬 환경 함정:
npm run check가check:lint에서 "0 errors" 로 실패 = 억제 주석 문구 변경(베이스라인 문자열 비교).dead-css허용 목록 추가는--update로 재생성해야 통과.gh issue comment본문의 작은따옴표 heredoc 안$(…)는 그대로 문자열이 된다. - Browser 팬이 숨겨진 상태에서는 클릭이 30초 타임아웃 —
javascript_tool로.click()호출이 우회. 스크린샷을 배치에 여러 장 넣으면 이미지가 누락된다(화면당 1장). - 유닛
tabSwitchLatencyProbe1건은 main에서도 같은 실패(Windows 줄바꿈 한정, #1160 기록).
5. 적용 결과
| 항목 | 결과 |
|---|---|
| 3-way 충돌 | 19곳 → 0 (RecordsDetail 13은 Phase 2 이식으로 흡수) |
| 되돌림(옛 본문이 지운 랜딩 수리) | 4건 → 0 |
| 미등록 마커 | 13건 → 0(designContract 그린) |
| 배송 그대로 넣을 때 빈 화면 | 풀업·플랭크·러닝 상세 "—" → 값 표시(recordMetricPhase1/3 그린, 렌더 스모크 5종 NaN·undefined 0) |
| 로컬 게이트 | npm run check 전 단계 + check-typescript-unused Phase 1·2·3 각각 통과(유닛 2,408건 중 1건 = main과 동일 실패) |
| 브라우저 실렌더 | 9화면 콘솔 오류 0(기존 svg height 경고 제외) |
| 복귀 뒤 추가 세트 보존 | 앱 강제 종료 시 소실 → 저장된 기록의 수정 초안으로 복원(Phase 6, 단위 테스트) |
| CI | PR #1167 3차 그린(scope·verify 4m34s·migration-smoke 10m38s — 1차 그룹 보드 e2e 마커 충돌, 2차 CASE-028 줄 id 단언 정정) |
| 미검증 | CASE-028 로컬 Supabase 실행(CI migration-smoke 레인 결과로 판정) · 다크 모드(앱 토큰 없음) · 실기기 safe-area는 사람 눈으로만 판단 가능 |
6. 이번 개선으로 향상된 것
1RM이 없는 종목도 상세가 디자인대로 채워진다
풀업(회)·플랭크(초)·러닝(거리)이 같은 화면 문법(등급 행·최고 기록·변화 그래프·PR·훈련 기록)으로 보이고, 값은 서버가 이미 계산해 둔 것을 그대로 쓴다.
세트마다 "얼마나 힘들었는지"가 한눈에
세션 상세에 수행 강도 칼럼이 색 구간으로 정렬되고, 숫자를 누르면 뜻을 설명하는 팝업이 뜬다.
종료 → 저장 → 완료가 카드 하나로 이어진다
"저장됐나?"를 카드가 답하고, 세트를 빠뜨렸으면 완료 화면에서 바로 돌아가 이어 기록할 수 있다(재저장 경로는 CASE-028이 고정).
구조적으로 남는 것
- 종목 상세 표기 규칙이
lib/recordMetric.ts한 곳(매퍼 kind 기반) — 다음 디자인 배송은 이 계약을 읽는다. - 이름 변경 저장 경로(
updateProfileDisplayName)와 그룹 보드 직행 함수(openGroupBoard). - 반입 절차 확인: 헝크 감사 +
git log -S도입 커밋 대조는 상시 절차(v65·v84·v85 세 번 연속 되돌림 검출).
남은 것
- 버티기·유산소의 "총 누적 시간/거리"는 서버 연간 요약(
yearStatsByYear)에 합계가 없어 "—". 서버가durationSeconds·distanceMeters합계를 주면 화면은 자동으로 채운다(별도 이슈). - 배송 노트의 종목군별 기준 거리 목록 확정 요청은 이미
cardio_reference_distances_v1(D4)로 정해져 있어 추가 작업 없음. - 데스크톱 종목 상세는 이번 표현 규칙 미적용(기존 잔여).