그룹(화이트보드·커피라운지) 신설 — 디자인 반입에서 백엔드·배선·차단 존중까지 (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.woff2200) - 설계서: 없음(배송 지시서 = 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 0 | zip 반입 감사(헝크 = 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 |
| pgTAP | 0 → 47단언(groups_v1) — CI migration-smoke 통과 |
| 마이그레이션 | 20260821800000 1건 — dry-run = 이 릴리스만 → Production push 완료 |
| Production 프로브 | 공개 RPC 3종 authenticated ✓·anon ✗, 내부 헬퍼 2종 authenticated ✗, 테이블 3종 존재·0행 (Management API 읽기 전용) |
| Vercel | Phase 1(498093d0)·Phase 2(cae57f86)·Phase 3(5604c0f3) Production success — 배포본 mobileRoot에 GroupScreen-*.js/group-*.css 배선·/fonts/Dovemayo_gothic.woff2 200 실측 |
| 화면별 배경 override | 10곳 → 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 범위 밖 — 스키마·게이트는 준비됨)
- 미팔로우 멤버 딥링크 = 친구 스코프 안 빈/오류 상태(가시성은 팔로우 게이트 유지 — 확장은 별도 보안 결정 필요)
- 오너 실기기 확인: 그룹 생성 → 초대 수락 → 보드 작성 → "이 운동으로 시작" 동선