Skip to content

그룹(화이트보드·커피라운지) 신설 — 디자인 반입에서 백엔드·배선·차단 존중까지 (2026-08-26)

  • 기간: 2026-08-26 새벽 단일 세션 (오너 지시 원문: "이거 이슈 하나 만들고, 새 디자인 적용하고 필요한 후속 기능 배선에 넣어줘 … 오너 결정사항은 너가 그냥 권고안대로 다 진행해")
  • 랜딩: PR #823(Phase 1, 498093d0) · PR #824(Phase 2, cae57f86 — 마이그레이션 20260821800000, Production db push+권한 프로브 검증 완료) · PR #825(Phase 3, 5604c0f3) — Vercel Production success 실측(그룹 청크 GroupScreen-*.js/group-*.css·폰트 /fonts/Dovemayo_gothic.woff2 200)
  • 설계서: 없음(배송 지시서 = delivery-20260826 v77 PR-DRAFT + 이슈 #822 Phase 계획 — "예상 효과·개선사항" 절 포함)
  • 정본: docs/contracts/group-props.md(화면 계약) · supabase/migrations/20260821800000_groups_v1.sql(저장·권한 — group_require_member_v1 멤버십 단일 관문 · group_content_visible_v1 차단 필터) · src/react/controllers/groupStore.ts(데이터·그룹 내 내비 소유)
  • 도구: 없음(레포 안 게이트만)
  • 게이트: supabase/tests/database/groups_v1.test.sql(pgTAP 47, migration-smoke 레인) · tests/react/groupScreens.test.mjs(렌더 계약 5) · designContract 그룹 마커 11종(뷰 4+액션 7)
  • 버그리포트: bug-report/bug-030-20260826.md(확인 대기 — 오너 실기기) — 홈→리포트 슬라이드 오버(⑦-a) 위에 시작 바 잔존(당일 오너 보고, PR #827 격납 수리) · bug-report/bug-031-20260826.md(확인 대기 — 오너 실기기) — 오버레이 로딩이 스켈레톤 대신 스피너 필(당일 오너 보고, PR #829 라벨 제거 + 왼→오 스와이프 홈 복귀 동승)
  • 계약: group-props.md §5 갱신(CreateSheet onSearchDirectory · 픽커 exerciseId 동반 · 마커 등재 상태)

Phase 현황

Phase내용상태
Phase 0zip 반입 감사(헝크 = manifest 1:1, 오염 0) · 이슈 #822 · Phase 계획
Phase 1디자인 15파일 3-way 반입 + 셸 등록 + 홈→리포트 슬라이드 오버(⑦-a) + 폰트 셀프호스팅(⑥)✅ PR #823 (498093d0)
Phase 2그룹 백엔드 v1(20260821800000) + 보강 2건(E2E 경로·차단 존중) + db push·프로브✅ PR #824 (cae57f86)
Phase 3컨테이너 배선(후속 ③④⑤⑦-b⑧ + 초대 서버 검색)✅ PR #825 (5604c0f3)
Phase 4검증·배포(remote-schema·Management API 프로브·Vercel 청크 실측)
Phase 5작업 기록·이슈 갱신✅ 이 문서

1. 배경

v76 친구 스코프까지의 소셜 표면은 전부 1:1 팔로우 기반(피드·친구 리포트·계획 반영)이었다. 같은 체육관·크루가 같은 날 같은 운동을 공유하는 표면이 없었고, 오너가 delivery-20260826(v77)으로 그룹 4화면(목록·커피라운지·화이트보드·작성)을 디자인 확정해 반입 지시했다. 서버는 그린필드 — 배송 PR-DRAFT가 후속 8건(①~⑧)을 명시했고, 오너 취침 위임으로 전 후속을 이 세션이 권고안대로 집행했다.

2. 문제 제기

그룹 저장소·권한이 서버에 전혀 없다

배송은 UI-only(NO Supabase imports 계약). 저장 계약·canWrite·가시성(후속 ③)이 없으면 4화면은 빈 상태 전시물이다.

탭바 개편이 기존 검증 동선을 끊는다

리포트(volume) 탭 자리에 그룹이 들어가며, E2E 2종(CASE-008·viewport-matrix)이 쓰던 "탭바 직접 클릭 → 리포트" 경로가 소멸했다. #823은 verify 레인만 돌아 잠복했고, #824의 full 레인(supabase 경로 자동 편입)에서 타임아웃으로 노출됐다.

신설 UGC 표면이 차단을 무시한다

그룹 대화·날짜 댓글·공지가 user_blocks를 보지 않으면 UGC 안전 v1(App Store 1.2 트랙)의 "차단 = 콘텐츠 숨김" 규약과 어긋난 채 새 표면이 열린다.

3. 해결 방안

원칙 (오너 위임 — 이슈 #822 D1~D4 권고안 채택, 2026-08-26)

  • D1 역할 = owner·writer·member, canWrite = owner|writer. 생성자 = owner, 초대 수락 = member. writer 부여 UI는 v1 제외(스키마·게이트만 준비).
  • D2 초대 = 생성 시 일괄(≤50), 후보 = 팔로잉 + search_user_profiles_v1 재사용.
  • D3 프리필 정규화 = 픽커 선택 시 exercise_id 동반 + 시작 시 뷰어 카탈로그 매칭(미해결 = 제외 + 개수 토스트).
  • D4 폰트 셀프호스팅(둘기마요 고딕 171KB, projectnoonnu noonfonts_2302@1.1 — CDN 런타임 의존 제거).
  • (위임 하 자체 판단) 차단 존중 보강 — 근거 = UGC 안전 v1 규약 정합, 머지 전 같은 마이그레이션에 편입.

접근

대안판단
가시성 = 멤버십 단일 관문(group_require_member_v1) — 모든 그룹 RPC 경유채택 — profile_feed_can_view_v1와 같은 지위. 그룹 스코프 RPC를 처음부터 그룹 단위로 설계해 BUG-029형 집계 스왑 여지 자체가 없다
보드 종목 = planned_sets형 정규 테이블기각 — 보드는 표시 산물(손글씨 1장). 검증 jsonb 스냅샷 + 이름 확정 텍스트로 충분, #703 서버 해석 이름 원칙은 "이름이 저장 시점에 텍스트로 실린다"로 충족
프리필 = 기존 세션 시작 경로(ensureCanonicalSessionDetail) 재사용기각 — DB 세션이 아닌 보드에 부적합. activeWorkoutCommands {kind:"prefill"} 신설(드래프트 직접 커밋)
author 카드 전용 함수 신설기각 — session_comment_author_json_v1 재사용(피드와 같은 모양·같은 클라이언트 사진 서명 경로)

4. 적용한 내용

Phase 1 — 디자인 반입 + 셸 등록 (#823)

배송 15파일 3-way 반입(감사 = delivery-manifest hunkAudit 1:1·오염 0. core.css = 배송이 main 전진분 수동 병합, main 주석 오타 수정 1단어만 main 유지. home.css = main 전진 1헝크와 배송 2헝크 비겹침). DashboardTab "group"·nav/drawer 카피·BottomNavProps.startLabel 등록(배송의 임시 캐스트 제거), keep-alive 판·lazy+워밍 청크 쌍·TAB_PANE_SKELETONS·WORKOUT_ORIGIN_TABS. 홈→리포트 슬라이드 오버(⑦-a — 리포트 화면 요소를 탭(volume)과 오버레이가 공유, idle이면 열 때 로드). 폰트 셀프호스팅(⑥). dead-css 대응(.gpi-cap 죽은 규칙 삭제·.pg-back 소비처 신설), 소스 앵커 재조준 2건(initialLoadPath — pending-changes 신고).

Phase 2 — 그룹 백엔드 v1 (#824, 20260821800000)

테이블 7종(groups·group_members·group_invites·group_boards·group_board_comments·group_notices·group_chat_messages) + 내부 헬퍼 4종(service_role 전용) + RPC 11종 + RLS(쓰기 = definer RPC only, 직접 읽기 = members·invites 본인 행만) + postcheck + pgTAP 47(비공허: 보드·댓글·대화 실존 확인 후 비멤버 차단, writer canWrite, 그룹장 삭제 권한, 차단 필터). 보강 2건: ① E2E 리포트 진입 = 드로어 경유 헬퍼(clickDrawerDashboardNav) ② 차단 존중(group_content_visible_v1 — 대화·댓글·공지 목록과 truncated 카운트, 멤버 목록은 구조라 유지).

Phase 3 — 컨테이너 배선 (#825)

repository 래퍼 11종(읽기 = socialRpcGuardingLifecycle, 쓰기 = callMutationRpc 관문 ②) + groupDomain(author 사진 서명 = withFriendPhotos 규약) + groupStore/groupController(feed social 스파인: 슬라이스 단조 토큰·오너 전환 리셋·에러 관문 ① 등재 코드 3종 비보고) + 탭 진입 1회 로드(screenPolicy group 분기). 4화면 실데이터 + 시작 바 프리필(⑤·⑦-b) + 멤버 딥링크(⑧ — 친구 스코프, 팔로우 게이트 유지) + data-lg 마커 11종(④) + CreateSheet onSearchDirectory(팔로잉+서버 검색 병합).

주요 결정과 그 근거

  • 그룹 내 내비게이션(목록→라운지→보드→작성)을 스토어가 소유 — 시작 바 게이트(그룹 탭+보드 열림+종목 있음+비진행)를 셸 영역이 스냅샷으로 계산해야 하기 때문.
  • 프리필 이름 = 뷰어 카탈로그의 표기(match.name) — #703 뷰어 해석 원칙 동조. 미해결 종목은 제외+토스트: 무ID 종목은 저장 계약(exerciseIdentity)이 거부하므로 조용한 실패를 사전 차단.

작업 중 드러난 것

  • verify 레인만 도는 프론트 PR은 탭바 구조 변경의 E2E 파손을 잠복시킨다 — full 레인이 뒤늦게 노출(#823→#824). 탭바·내비 구조 변경 반입 시 e2e의 data-lg-option-value 사용처 grep을 반입 턴에 할 것.
  • git worktree에서 git stash → checkout -B → stash pop 직후 인덱스가 전 파일 삭제로 스테이징되는 현상 1회(디스크 온전) — git reset(mixed)으로 복구. 트리 동일 재기점은 stash 없이 checkout -B만으로 안전.
  • 소스 슬라이스 앵커 게이트는 SSR HTML의 indexOf("클래스")도 파일 앵커로 취급 — HTML 순서 검증은 정규식으로 쓸 것.
  • pgTAP는 첫 CI에서 한 번에 통과 — 비공허 구성(차단 전 보임 확인)과 fixture 함정 3종(sessions source_ref 등)은 기존 기록(friend-scope-data-v2) 답습이 유효했다.

5. 적용 결과

항목결과
배송 감사전 헝크 = manifest 1:1, 오염 0
유닛 테스트1999 → 2004(그룹 렌더 5 신설), 실패 0
pgTAP0 → 47단언(groups_v1) — CI migration-smoke 통과
마이그레이션20260821800000 1건 — dry-run = 이 릴리스만 → Production push 완료
Production 프로브공개 RPC 3종 authenticated ✓·anon ✗, 내부 헬퍼 2종 authenticated ✗, 테이블 3종 존재·0행 (Management API 읽기 전용)
VercelPhase 1(498093d0)·Phase 2(cae57f86)·Phase 3(5604c0f3) Production success — 배포본 mobileRoot에 GroupScreen-*.js/group-*.css 배선·/fonts/Dovemayo_gothic.woff2 200 실측
화면별 배경 override10곳 → 0곳(.screen 기본값 1곳)
리포트 진입탭 1경로 → 드로어 + 홈 프로필 탭 슬라이드 2경로
오너 실기기미검증 — 그룹 생성→초대→보드→시작 동선 확인 대기

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

그룹 동선이 0에서 전 구간 개통

목록(초대 확인 카드) → 커피라운지(멤버·공지·자유 대화) → 화이트보드(날짜별 1장·손글씨·달력 점프·날짜 댓글) → 작성/수정 → 셸 시작 바 "[그룹명]의 M월 D일 운동으로 시작" 프리필까지.

새 화면 기본 지면 = 순백

글로우 지면 폐지 + .screen 기본 배경 승격 — 새 화면이 override 없이 순백. 화면별 배경 선언 10곳 → 0곳.

신설 UGC 표면이 차단 규약과 정합

그룹 대화·댓글·공지가 user_blocks 양방향을 숨긴다 — App Store 1.2 대응 표면이 넓어져도 규약이 유지된다.

구조적으로 남는 것

group_require_member_v1(멤버십 관문)·group_content_visible_v1(차단 필터) — 다음 그룹 표면의 단일 게이트. pg-slidein/pg-overlay 페이지 전환 공용 프리미티브(그룹 보드·홈→리포트 2소비처). activeWorkoutCommands {kind:"prefill"}(보드 외 프리필 소스 재사용 가능). group-props 화면 계약 + designContract 마커.

남은 것

  • writer 부여·사후 초대·그룹 탈퇴/해산 UI (v1 범위 밖 — 스키마·게이트는 준비됨)
  • 미팔로우 멤버 딥링크 = 친구 스코프 안 빈/오류 상태(가시성은 팔로우 게이트 유지 — 확장은 별도 보안 결정 필요)
  • 오너 실기기 확인: 그룹 생성 → 초대 수락 → 보드 작성 → "이 운동으로 시작" 동선