Skip to content

피드 카드 복합 종목 합치기 — 동작 두 줄로 쪼개진 카드에서 세션 상세와 같은 한 종목 표시까지 (2026-09-03)

  • 기간: 2026-09-03 (세션 1개, 오너 보고 "이거 복합종목으로 했는데, 피드 세션카드에 별개 종목으로 표시되는 문제 해결해줘")
  • 랜딩: PR #1185(Phase 1~3, c5f0aead) — 마이그레이션 20260905235900_feed_presentation_composite_meta_v1.sql, Vercel 자동 배포. Production 적용 = 릴리스 레인
  • 설계서: 없음(이슈 #1182 본문의 Phase 계획·"예상 효과·개선사항" 절이 정본)
  • 정본: restorePresentationMains·presentationMainSetCount(src/react/services/compositeExerciseModel.ts) · 카드 응답 키 composite_meta·position(src/react/types/screenRpc.ts)
  • 도구: Production 조회(Management API 쿼리 러너, 세션 a380c940…)
  • 게이트: tests/react/feedCompositeMains.test.mjs(7건) · pgTAP following_feed_v1.test.sql(+8)
  • 버그리포트: bug-069-20260903.md
  • 계약: 피드/검색 카드 응답에 추가 전용 키 3개(계약 버전 불변)

Phase 현황

Phase내용상태
Phase 1서버: 카드 엔진 재발행 — 종목 카드 position·composite_meta, 세트 토큰 position + pgTAP✅ #1185
Phase 2앱 계약·합치기: 어댑터 검사, 조립이 묶음 정보 전달, 카드 조립 4곳이 세션 상세와 같은 함수로 합침 + 세트 수 재계산✅ #1185
Phase 3표시: 피드·데스크톱 홈/검색 카드의 복합 한 줄 표기, 데스크톱 검색 동작 일치✅ #1185
Phase 4랜딩·기록: CI 1회·랜딩 잠금·문서✅ 이 문서 / Production 확인은 5절(릴리스 v0.15.0)

1. 배경

복합 종목(예: 하이 클린 + 저크)은 서버에 동작마다 한 행으로 저장되고 composite_meta의 묶음 키로 묶인다. 세션 상세와 하루 상세는 이 묶음 키로 한 종목으로 다시 합치며, #1151(2026-09-02)에서 합쳐진 세트의 표기("50kg × (3+1)")까지 정리했다. 피드·검색 카드는 별도 서버 함수가 만든 축약 응답을 쓰는데, 이 응답은 그 범위 밖이었다.

2. 문제 제기

카드 응답에 묶음 정보가 없어 앱이 합칠 수 없었다

카드용 서버 함수 get_session_presentation_mains_json_v2_engine은 종목 행을 하나씩 나열하며 composite_meta와 세트 position을 싣지 않았다. Production 실측(2026-09-02 "역도" 세션)에서 하이 클린·저크 두 행이 같은 묶음 키를 갖고 있음을 확인했지만, 카드 응답만 보면 두 행이 한 종목이라는 것을 알 수 없다.

카드 머리 세트 수가 세션 상세와 달랐다

서버 집계표(user_exercise_session_rollups)는 동작 행마다 한 줄이라 복합 종목 세트가 동작 수만큼 중복된다. 카드 21세트 vs 세션 상세 16세트.

3. 해결 방안

원칙

오너의 명시 결정 없음(수리 건). 이슈 본문 계획대로: 서버는 묶음 정보만 더 싣고, 앱은 세션 상세의 합치기 함수를 재사용한다.

접근

판단
서버가 composite_meta·position을 싣고 앱이 합친다채택 — 추가 전용 키, 검증된 합치기 로직 재사용, 옛 응답은 종전과 동일
서버 함수가 합쳐진 종목을 바로 만든다기각 — 세트 토큰 계약 변경·합치기 규칙 SQL 재구현
앱만 고친다기각 — 응답에 묶음 정보가 없어 추측 불가
집계표 자체를 복합 기준으로보류 — 통계 전반이 쓰는 표. 카드 머리만 합쳐진 기준으로 재계산(잘린 카드는 서버 값 유지)

4. 적용한 내용

Phase 1 — 서버 (#1185, 마이그레이션 20260905235900)

카드 엔진 재발행: candidate_mainscomposite_meta, 종목 카드에 'position'·'composite_meta'(bounded_home_composite_meta로 크기 제한), 세트 토큰에 'position'. 감싸는 함수(get_session_presentation_mains_json)는 카드·토큰을 그대로 통과시키므로 재발행 없음. pgTAP following_feed_v1에 백스쿼트 + 풀업 복합 픽스처와 8단언(plan 19 → 27).

Phase 2 — 앱 계약·합치기 (#1185)

  • screenRpcAdapters.ts: 피드/검색 카드의 position·composite_meta, 세트 position을 있을 때만 검사(허용 키 = 달력 묶음 키 목록, 768바이트, 정수).
  • barbelicMappers.buildProfileFeedItems: compositeMeta·position·setsTruncated·mainsTruncated 전달.
  • compositeExerciseModel.ts: restorePresentationMains(묶음 정보가 있고 모든 묶음이 동작 2개 이상·종목 id 유효일 때만 restoreCompletedCompositeExercises 호출, 아니면 입력 그대로) · presentationMainSetCount(합쳐진 기준 메인 세트 수, 잘린 카드는 null).
  • mobileFeedViewMappers.buildMobileFeedPosts·barbelicViewMappers.lgDesktopProfileFeed: 합치기 + 합쳐진 카드만 세트 수 재계산.

Phase 3 — 표시 (#1185)

FeedPost.FdAttachformatSetRunsWithComposite, 데스크톱 DkH3Mains·DkSrchMainsformatCompositeSetLine ?? formatSetMeasurements. 데스크톱 검색은 합쳐진 종목의 동작 이름·id로도 일치(dkSrchMainMatches).

주요 결정과 그 근거

  • 합치기는 화면 조립 계층에서: buildProfileFeedItems 결과는 종목 훈련 기록(#1002)·모바일 세트 검색이 동작별로 읽으므로 그대로 두고, 카드 조립 2곳에서만 합친다.
  • 세트 수 재계산은 합쳐진 카드에서만: 복합이 없는 카드는 서버 값 그대로(집계 규칙 차이의 파급을 막는다). 잘린 카드는 전체를 알 수 없으므로 서버 값 유지.
  • 동작 하나만 남은 묶음은 합치지 않는다: 합치기 함수가 그 묶음을 조용히 버리는 것보다 동작 행이 따로 보이는 쪽이 낫다.

작업 중 드러난 것

  • dual-key 게이트: 합치기 도우미와 카드 정규화에서 camel ?? snake 이중 키를 쓰면 게이트가 막는다 — 경계(buildProfileFeedItems)가 camel로 확정하므로 camel만 읽는다.
  • npm run checktabSwitchLatencyProbe 실패 1건은 수정 전 main에서도 같은 Windows 줄바꿈 원인으로 실패(#1161 계열), 이 변경과 무관.
  • 워크트리 node_modules는 PowerShell New-Item -ItemType Junction으로(cmd mklink는 경로 인용 문제로 실패).

5. 적용 결과

항목결과
실측 세션 모양 픽스처의 카드 종목 줄3줄(하이 클린 / 저크 / 클린 풀) → 2줄(하이 클린 + 저크 / 클린 풀) — 단위 테스트
카드 머리 세트 수(픽스처)16(5+5+6) → 11(5+6) — 단위 테스트
세트 토큰"100kg 1x1" 두 줄 → "100kg (1+1)x1" 한 줄 — 단위 테스트
옛 응답(키 없음)종전과 동일(종목 3줄·서버 세트 수) — 단위 테스트
잘린 카드합치되 세트 수는 서버 값 유지 — 단위 테스트
pgTAP8단언 — CI에서 실행
Production 카드 응답에 composite_meta릴리스 v0.15.0(2026-09-03) 뒤 확인: 마이그레이션 20260905235900 적용, 카드용 서버 함수 본문에 composite_meta·position 포함(읽기 전용 조회). 카드 응답 직접 호출은 조회 계정 권한(42501)으로 미실측
어제 "역도" 카드의 실제 표시(4줄·21세트 → 3줄·16세트)미검증(자동 검증 밖) — Production 반영됨(v0.15.0), 픽스처는 실측 데이터와 같은 모양

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

복합 종목이 카드에서도 한 종목으로 보인다

모바일 피드·친구 피드·데스크톱 홈·데스크톱 검색 카드가 세션 상세와 같은 이름("하이 클린 + 저크")과 세트 구성으로 그려진다.

카드 머리와 세션 상세의 세트 수가 같아진다

복합 종목이 든 카드의 세트 수가 동작 수만큼 부풀지 않는다.

구조적으로 남는 것

카드 응답이 세션 상세와 같은 묶음 정보를 싣고, 카드류 조립은 restorePresentationMains 하나를 공유한다. 복합 픽스처가 pgTAP·단위 테스트에 고정됐다.

남은 것

  • 릴리스 v0.15.0(2026-09-03)으로 Production 반영, 이슈 #1182 [반영완료]. 카드 응답 직접 실측만 권한 문제로 남았다(앱 화면에서 확인되는 항목).
  • 집계표 세트 수의 복합 중복(통계·리포트)은 범위 밖 — 필요 시 별도 이슈.