Skip to content

친구 스코프 데이터 v1 — 랜딩 빈 상태 카드 3장에서 실데이터·스토어 정본까지 (2026-08-25)

  • 기간: 2026-08-25 (세션 1 — 친구 스코프 v76 반입·배선과 같은 세션, 오너 보고 원문: "상대방 프로필 사진 들어갔을때 이렇게 나옴 (상대방 프로필 데이터가 없는듯)" + 오너 제안 "스코프 하나로 통합은 지금 하는게 맞지 않을까?" — 이슈 #792)
  • 랜딩: PR #795(Phase 1~4, 2b76117d) + PR #799(Phase 5-1 진입 애니 1회, 2d397ca8) — 마이그레이션 20260821770000_following_pr_overview_v1 1건(Production db push 수동 반영 2026-08-25 — 이 레포는 자동 배포 없음), Vercel 배포 확인(CI pass)
  • 설계서: 없음 — Phase 계획·예상 효과·개선사항 표는 이슈 #792 본문
  • 정본: controllers/friendScopeStore.ts(친구 스코프 상태 전체 — 정체·탭·슬라이스 3종) · feed-props.md §친구 스코프 전환 "컨테이너 상태 정본" 불릿 · 서버 allowlist 관례 = following_* 클레임 스왑 래퍼 + profile_feed_can_view_v1 단일 게이트(3례: 볼륨·달력·PR)
  • 도구: 없음
  • 게이트: tests/react/friendScopeStore.test.mjs 8건 · pgTAP following_pr_overview_v1.test.sql 13건 · followingStore.test.mjs 전송 취소 1건 · CASE-010(브라우저 저니, 이번에 소음 수리로 재green)
  • 버그리포트: 없음(기능 트랙 — CASE-010 건은 오너 미보고·CI 적발이라 리포트 대신 이 문서 4절에 기록)
  • 계약: feed-props.md §친구 스코프 전환에 컨테이너 정본 불릿 추가

Phase 현황

Phase내용상태
Phase 1서버 get_following_pr_overview_v1 + pgTAP 13✅ PR #795 (2b76117d)
Phase 2friendScopeStore 통합(구 friendReportStore 흡수·planAdoption 차용 해제)✅ 〃
Phase 3랜딩 실데이터 배선(종합 카드·티커·보유 기록 상위 보드)✅ 〃
Phase 4게이트·CI·머지 (+CASE-010 소음 수리)✅ 〃
Phase 5오너 실기기 확인 1차 — 피드백 2건✅ 원인 확정·조치(아래 5-1·5-2)
Phase 5-1진입 애니 반복 재생 수리(판 내 Suspense+entering 1회 클래스)✅ PR #799 (2d397ca8)
Phase 5-2랜딩 데이터 없음 = 마이그레이션 Production 미push → db push 반영✅ 프로브·권한 검증
Phase 6오너 실기기 재확인(랜딩 실데이터·애니 1회·달 이동·왕복)⬜ 오너

1. 배경

친구 스코프 v76이 4화면 스코프를 랜딩시켰지만, 랜딩(프로필 탭)의 세 블록(종합 기록 카드·PR 티커·주요 종목 기록)은 소유자 전용 get_pr_overview 소산이라 친구용 데이터원이 없어 빈 상태 카드 3장이 떴다 — 오너 실기기 확인(Phase 6)에서 즉시 보고됐다. 클라 쪽도 친구 데이터가 3곳에 흩어져 있었다: 리포트=friendReportStore, 일지=계획 반영 v1의 planAdoptionStore를 차용, 탭=mobileApp 로컬 상태.

2. 문제 제기

친구 랜딩의 데이터원이 없다

소유자 PR 개요 파이프라인(체인 전부 auth.uid() 기준)에 친구용 등가물 부재. 서버 표면엔 볼륨·달력 래퍼만 있었다.

두 기능이 한 상태 슬롯을 공유한다

친구 일지가 planAdoptionStore.friendCalendar를 빌려 씀 — 계획 반영 v1 UI가 배선되는 순간 서로의 달력 상태를 덮는 충돌 예약. 세 번째 소스(PR 개요) 추가 시점이 통합 최저비용 시점이었다(오너 제안으로 범위 확정).

(CI 적발) 리로드가 끊은 RPC가 오류로 적재된다

migration-smoke의 CASE-010(피드 스테일 청크 복구)이 RED: 복구 리로드가 비행 중이던 list_following_v1을 끊고(트레이스 status −1), followingStore가 이를 rpc_failure로 적재 → '기록 0' 단언 실패. 저니 자체는 정상(리로드 후 재호출 200). 브라우저 저니 레인은 마이그레이션 PR에서만 돌아 UI 머지 구간(#750~#790)은 이 레인 미통과로 랜딩돼 있었다 — 잠복 소음의 첫 적발.

3. 해결 방안

원칙 (오너, 2026-08-25)

  • D1: 이번 케이스 땜질이 아니라 소셜 확장 구조로 — following_* 래퍼 allowlist(표면당 공개 결정+pgTAP) + 가시성 정책은 profile_feed_can_view_v1 한 곳.
  • D2: 스토어 통합은 지금(오너 제안 채택) — 세 번째 래퍼를 구식으로 얹고 나중에 갈아엎는 것보다 싸다.

접근

대안판정
클레임 스왑 래퍼 3례째채택 — 체인 무변경 재사용(이중 유지보수 0), 기준표 성별도 스왑 아래서 친구 기준(의도된 의미)
범용 "친구 자격 실행" 디스패처기각 — 이후 모든 소유자 RPC가 자동으로 팔로워 노출되는 구조(프라이버시 allowlist 붕괴)
friendScopeStore 1개(슬라이스 3종)채택 — 늦은 응답 폐기 토큰을 슬라이스별로, 열기=랜딩(report·pr 즉시)·달력은 일지 탭 lazy+달 dedupe
친구 즐겨찾기 노출(보드 행용)기각 — 비공개 데이터. 대신 보유 기록 상위 6을 favoriteIds 자리(순서 리스트)로 공급 — 매퍼 무변경
CASE-010: 관문(reportedUserError) 전역 취소 억제보류 — 관문 의미 변경은 관측성 트랙 소관. 이번엔 소셜 로더 4종에 국소 정규화(기존 stats 디스패처 선례 문법)

4. 적용한 내용

Phase 1 — 서버 (#795, 20260821770000)

get_following_pr_overview_v1(p_user_id, p_as_of) — 트랜잭션 로컬 sub 클레임 스왑으로 get_pr_overview 체인 그대로, profile_feed_can_view_v1 게이트(비팔로우 42501·null 22023). pgTAP 13(payload 동일성·클레임 복원·본인/비팔로우/미인증·anon 실행 불가).

Phase 2 — friendScopeStore 통합

friendScopeStore(정체+탭+report·pr·calendar 슬라이스) 신설, friendReportStore/Controller 삭제(행동 계약 테스트 이관), planAdoption 차용 해제(같은 달력 RPC를 자기 슬라이스로), 히스토리 레이어 friendReportfriendScope(#friend-scope, navigationStore 화이트리스트 동조), API 3층(loadFollowingPrOverview) 배선.

Phase 3 — 랜딩 배선 (mobileApp)

친구 PR 개요 → buildPrScreenData+buildMobileHomeSupplement(내 홈과 같은 투영). 2-pass: 1차 투영의 기록 보유 종목 상위 6 id를 favoriteIds로 재투영 → 주요 종목 기록 보드가 채워진다. 성별은 payload 기준표 첫 행에서 역산(표는 서버가 친구 프로필 성별로 한정). 조회 전용(편집·종목 점프 콜백 미공급).

Phase 4 중 — CASE-010 소음 수리

소셜 RPC 4종에 문서 수명주기 취소 정규화(socialRpcGuardingLifecycle) → AbortError로 던져 스토어 관문 앞에서 기록·문구 없이 통과. followingStore·friendScopeStore에 AbortError 가드.

작업 중 드러난 것 (다음 세션 주의)

  • 수명주기 신호는 요청 "전"에 무장해야 한다 — catch 시점에 screenRpcDocumentLifecycleSignal()을 처음 만들면 이미 지나간 pagehide를 못 봐 aborted=false(1차 수리가 이걸로 재실패).
  • supabase-js는 네트워크 실패를 reject가 아니라 { error }(code 빈 문자열)로 resolve한다 — throw 경로 가드만으론 안 잡힌다. 저장된 에러 이벤트의 error_code: ""가 이 경로의 지문.
  • 브라우저 저니 레인(CASE-*)은 마이그레이션 PR에서만 돈다 — UI 전용 머지 구간은 미통과로 쌓인다. 타이밍 민감 케이스는 full-ci 라벨 주기 실행 가치(오너 판단 항목, 이슈 #792 코멘트).
  • 마이그레이션은 머지돼도 Production에 자동 배포되지 않는다 — 머지 직후 supabase db push가 트랙 체크리스트다. 이번에 누락해 오너 실기기에서 PGRST202로 드러났다(client_error_events 3건이 테스트 시각과 일치 — 프로브 기법의 실증).
  • lazy 화면을 새 판(오버레이) 안에 마운트할 때는 Suspense 경계를 판 안에 둘 것 — 경계가 판 밖이면 첫 서스펜션이 판을 display 토글해 판의 CSS 진입 애니가 재시작한다(오너 보고로 적발). 진입 애니는 1회용 클래스(animationend 해제)로 두는 게 구조적 안전.

5. 적용 결과

항목결과
친구 랜딩 데이터 블록빈 상태 카드 3장 → 종합 기록 카드·PR 티커·보유 기록 상위 보드(친구 실데이터)
친구 데이터 상태스토어 3곳 분산(1곳 타 기능 차용) → friendScopeStore 1곳
계획 반영 v1 배선 시 충돌 예약1건(friendCalendar 슬롯 공유) → 0건
following_* 서버 표면래퍼 2 → 3 (allowlist 관례 유지, 게이트는 계속 한 곳)
테스트1983 → 1991(스토어 8·전송 취소 2, 구 스토어 6 이관) · pgTAP +13
CIscope·verify·migration-smoke(브라우저 저니 17종·pgTAP) 전부 pass — CASE-010은 2회 실측 원인 교정 끝에 green
미검증친구 랜딩 실데이터 실기기 표시·진입/퇴장 애니 각 1회 체감·일지 달 이동·왕복 → 오너 실기기 재확인 대기(Phase 6)

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

친구 프로필이 "빈 껍데기"에서 "그 사람의 기록 페이지"가 됐다

등급·티커·상위 기록이 친구 실데이터로 차고, 이후 소셜 화면 추가는 "래퍼 하나(공개 결정+pgTAP) + 슬라이스 하나"로 정형화됐다.

구조적으로 남는 것

  • following_* allowlist 관례 3례째 — 팔로워 노출은 표면당 명시 결정, 정책 확장(비공개 계정·차단 등)은 profile_feed_can_view_v1 한 곳.
  • friendScopeStore — 소셜 화면 증설 시 슬라이스만 늘리는 자리.
  • 소셜 RPC 수명주기 취소 정규화 — "리로드가 끊은 요청은 오류가 아니다"가 코드와 테스트로 고정.

남은 것

  • 오너 실기기 재확인(Phase 6): 친구 랜딩 실데이터·진입/퇴장 슬라이드 각 1회·일지 달 이동·리포트/피드 왕복.
  • HomeScreen 친구 모드 1인칭 문구 2건(친구 기록 0일 때 노출) — 디자인 측 후속.
  • 친구 일지 볼륨 비표기(달력 페이로드 한계) · 관문 전역 취소 억제 여부(관측성 트랙 판단) · 브라우저 저니 레인 주기 실행(오너 판단).