피드 자유 기록 카드 — 자유 기록만 있는 세션이 피드에서 빈 줄로 보이던 문제 (2026-08-22)
- 기간: 2026-08-22 (오너 보고 → 같은 날 수리)
- 랜딩: PR 1건 — 마이그레이션
20260821440000(get_profile_feed_v2_engine에notes[]·notes_truncated추가, 추가 전용) + 클라이언트(어댑터·매퍼·모바일FdPost·데스크톱DkH3SessionPost·CSS 2줄) + pgTAP 1파일·계약/매퍼/렌더 테스트 4건. Production 적용은 마이그레이션 1건, 클라이언트는 main 배포로 반영 - 정본:
docs/data/app-screen-rpc-contract.md(+docs/ko/…)get_profile_feed응답 형태·items[].notes행 — 카드는 자유 기록 항목을 싣는다;docs/contracts/feed-props.mdposts[].notes;tests/react/screenRpcContracts.test.mjs피드 픽스처(notes포함) · pgTAPprofile_feed_note_entries.test.sql
1. 증상
자유 기록(entry_kind = 'note', 예: WOD 설명)만 적고 저장한 세션이 피드에서 첨부 카드 없이 "운동 세션 · 0kg · 0세트 · 0종목" 한 줄로만 보였다. 사용자가 적은 제목·내용이 피드 어디에도 없었다.
2. 원인
get_profile_feed의 카드는 get_session_presentation_mains_json이 만든 mains만 싣는데, 이 함수는 main/top 세트를 가진 session_exercises만 고른다. 자유 기록 항목은 세트가 없으니 항상 빠지고, 클라이언트 (FdPost·DkH3SessionPost)는 mains가 비면 첨부 카드를 열지 않고 KPI 한 줄(볼륨·세트·종목 모두 0)만 그렸다.
3. 수리
- 서버(추가 전용):
get_profile_feed_v2_engine을 베이스라인 정의 그대로 다시 내면서 세션마다 lateral 하나를 보탰다 —notes: [{ id, title(120 B), note(240 B), presentation_truncated }](entry position asc, 최대 4개) +notes_truncated(5번째가 있으면 true).contract_version은 3 유지: 키가 추가 전용이고 클라이언트 어댑터는 있을 때만 검증하므로 구 번들도 그대로 읽는다.get_session_presentation_mains_json·get_session_search는 무변경. - 클라이언트: 어댑터가
notes/notes_truncated를 있을 때만 검증(4개 상한·바이트 상한·절단 불변식) →buildProfileFeedItems·lgDesktopProfileFeed·buildMobileFeedPosts가notes를 나른다 → 모바일FdPost· 데스크톱DkH3SessionPost가 mains가 없어도 notes가 있으면 첨부 카드를 열고, 종목 행 뒤에 레터를 이어 "제목(없으면 '자유 기록') + 메모 앞 2줄" 행을 그린다. 세트 KPI 토큰은 main 카드가 있을 때만 단다(자유 기록만 있는 세션에 "0세트"를 붙이지 않음). 볼륨 토큰은 원래부터 load·reps 프로필일 때만. - 테스트: pgTAP(자유 기록만 있는 세션 →
notes1개·mains[]; 5개 → 4개+notes_truncated; 없음 →[]), 어댑터(있을 때 검증·없을 때 통과·상한·절단 불변식·바이트), 매퍼(데스크톱·모바일), 렌더(피드·홈이 제목과 앞 2줄을 그리고 0kg/0세트/0종목을 내지 않음).
4. 배포 순서
클라이언트는 notes가 없어도 동작하고 서버는 구 번들이 모르는 키만 더하므로 순서 제약이 없다. 기록은 "main 머지 → Vercel 배포 → supabase db push" 순.