v78 반입·후속 배선 — 화이트보드 작성이 계획 편집기가 되기까지 (2026-08-26)
- 기간: 2026-08-26 (1세션, 오너 지시 원문: "이거 이슈 하나 생성해서 후속 배선작업좀 진행해줘")
- 랜딩: PR #832(Phase 0,
483e5691) · PR #833(Phase 1,3570980d— 마이그레이션 20260826100000, Production db push+프로브 검증 완료) · PR #834(Phase 2,16ecaa31) · PR #835(Phase 3,7580ddaa) — 전부 Vercel Production success 실측 - 설계서: 이슈 #831 본문(Phase 계획 + 예상 효과·개선사항)
- 정본:
group_validated_board_exercises_v1(20260826100000 — kind:"note" 분기) ·docs/contracts/group-props.md§4·§5 ·groupStore(kind 왕복·uuid 가드) ·mobileApp컴포저 lazy 쌍(workout.css 동반) - 도구: 없음(반입물 zip 매니페스트 감사는 수동 대조)
- 게이트: pgTAP
group_board_note_kind.test.sql(13) ·groupBoardNoteRoundtrip.test.mjs(3) ·groupScreens.test.mjsv78 계약 갱신(→ 프리픽스 재발 차단 doesNotMatch 포함) - 버그리포트: 없음(기능 배선 트랙 — 오너 보고 결함 아님)
- 계약: group-props.md §5(컴포저 = 계획 편집기 재사용·kind:"note" 왕복) · §4(보드 서체 셀프호스팅) · group_boards 스냅샷 코멘트(kind 편입)
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 0 | v78 반입(6파일 21헝크) + CI 게이트 수리 3건 | ✅ PR #832 (483e5691) |
| Phase 1 | 서버 자유 기록 수용 — 검증기 재정의 + pgTAP 13 + Production 적용 | ✅ PR #833 (3570980d) |
| Phase 2 | 프런트 배선 ①~⑤ + 라운드트립 테스트 3 | ✅ PR #834 (16ecaa31) |
| Phase 3 | 보드 서체 나눔스퀘어라운드 셀프호스팅(B·EB 2종) | ✅ PR #835 (7580ddaa) |
| Phase 4 | Production 실측(청크 마커·폰트 200) + 본 기록 | ✅ |
1. 배경
그룹 신설(이슈 #822, 작업 기록) 직후 디자인 전달 v78(delivery-20260826b, base 5604c0f3)이 도착했다. 내용은 두 가지 오너 지시의 구현 — ① 화이트보드 작성 화면을 운동 계획 작성과 동일 화면으로(자체 gpc- 폼 폐기 → UiWorkoutRecord(planMode) + UiWorkoutPicker 재사용, 자유 기록 kind:"note" 동반) ② %(기준 1RM) 입력 개편(pctSel 시각 상태 + 기준 종목 픽커 드롭다운 → 센터 모달). 전달물은 화면 계층만 바꾸고, 컨테이너·서버 배선 7건을 "Codex 후속"으로 명시해 넘겼다.
2. 문제 제기
자유 기록은 저장 자체가 서버에서 거부된다
group_validated_board_exercises_v1(20260821800000)은 종목당 세트 1..30을 강제하고 미지 키를 떨어뜨린다. kind:"note"(sets:[]) 항목은 22023으로 거부되고, 통과하더라도 kind가 소실돼 수정 재진입에서 일반 종목으로 격하된다. memo 상한도 80자 vs 자유 기록 본문 500자.
컴포저가 workout 표면에 의존하는데 배선이 없다
lazy 쌍은 group.css만 동반 — workout.css는 유휴 워밍 경합에만 기대 콜드 진입 시 스타일 부재. catalog는 {id,name} 축약본(synonym 검색·기록 프로필 상실), onAddCustomExercise·profile·oneRms·exerciseHistory 미배선.
workout.record 마커가 그룹 keep-alive 판 안에서 발화한다
nav.css의 탭바 숨김([data-lg-view^="workout."])에 .tab-pane.off 제외가 없어, 숨은 그룹 판에 컴포저가 남으면 다른 탭에서도 탭바가 사라질 수 있었다.
보드 서체가 네이버 CDN @import
CDN 런타임 의존 제거 정책(Dovemayo 셀프호스팅 선례) 위반.
3. 해결 방안
원칙
- 서버 먼저(후방호환): kind 없는 구 payload는 기존 규칙 그대로 두고 검증기에 note 분기만 신설 → 클라가 kind를 보내기 전에 서버가 받게 한다.
- 쿠리어 최소 스코프: 반입은 zip 그대로, 수정은 CI 파손 수리만(3건 — 아래 4절).
- 경계에서 확정: dual-key 게이트·ts-boundary 게이트가 요구하는 방향 그대로 — 새 이중 키/any 등재 대신 입력 모양·타입을 확정.
접근
kind 왕복은 서버 에코 + 클라 보존의 2겹(어느 한쪽 소실 = 격하 재발이므로 pgTAP·유닛 양쪽에 잠금). 탭바 가드는 마커 축 유지 + :not(.tab-pane.off *)(기존 오버레이 숨김 3종과 같은 문법) — 컴포저 전용 클래스 신설안은 마커-소비 규칙을 이원화해 기각. 폰트는 보드가 실제 쓰는 B·EB 2종만 반입(전 굵기 4종 반입안 기각 — 사용처 없음).
4. 적용한 내용
Phase 0 — v78 반입 (#832)
6파일 21헝크(매니페스트 대조 일치·오염 그렙 0·base 교집합 0). CI 파손 수리 3건 동반: ① e.measurementType ?? e.measurement_type 이중 키 2곳 제거(카탈로그 경계가 camelCase 확정 — check:dual-key) ② groupScreens.test.mjs 렌더 계약을 v78로 갱신(구 폼 앵커 '화이트보드 수정'·자체 픽커 onPick → workout.record 마커 동거·수정 완료 필·자유기록 카드 복원; 신규 파일이라 pending-changes 선고 불요 — 게이트가 직접 알려줌) ③ useState<any> 2곳을 string | null로(check:ts-boundary-gate — WorkoutFlow는 등재 파일이지만 GroupComposer는 아님).
Phase 1 — 서버 자유 기록 수용 (#833, 마이그레이션 20260826100000)
group_validated_board_exercises_v1 전체 재정의: kind:"note" = name 1..200자·memo ≤500자·sets 항상 []·exercise_id 불허·kind 에코. kind 미지정 경로 문자 그대로 불변, exercise|note 외 22023. group_boards 코멘트·함수 코멘트 갱신, postcheck는 손댄 동작만(에코+기존 경로). pgTAP 13(왕복·경계 500자·거부 5종). 머지 후 supabase db push Production 적용 → Management API 읽기 전용 프로브로 schema_migrations 최신 20260826100000·검증기 에코 실측.
Phase 2 — 프런트 배선 ①~⑤ (#834)
① lazy 쌍에 workout.css ② exerciseCatalog 직결 + 콜백 4종, groupComposerCatalog 폐기 ③ nav.css :not(.tab-pane.off *) ④ toBoard/saveBoard kind 보존 ⑤ exercise_id uuid 모양 가드(소문자 정규화, 슬러그는 떨궈 저장 지속) + 프리필 note 제외(빼고-시작 개수 미집계) + 시작 바 게이트 non-note 기준. 신설 groupBoardNoteRoundtrip.test.mjs 3건.
Phase 3 — 폰트 셀프호스팅 (#835)
@import(hangeul.pstatic.net) → public/fonts 로컬 @font-face 2종. NanumSquareRoundB.woff2(232,072B)·NanumSquareRoundEB.woff2(226,584B), 원본 네이버 공식 배포·SIL OFL·font-display: swap. group-props.md §4 서체 서술 현행화. Dovemayo_gothic.woff2는 참조 0이나 유지(되돌릴 여지).
작업 중 드러난 것
- 반입 PR도 CI 게이트 3종을 연달아 맞는다(dual-key → 렌더 계약 테스트 → ts-boundary). v78처럼 화면을 전면 재작성한 전달물은 반입 전에 이 3종을 로컬 선행하는 것이 싸다.
check:test-manifest는 감사 manifest 밖(신규) 테스트의 pending-changes 선고를 오히려 거부한다 — 선고가 필요한지 게이트를 먼저 돌려보면 알려준다.- 워크트리는 supabase 링크가 없다 — primary의
supabase/.temp를 복사하면db push --linked가 동작한다(dry-run으로 버전 선점 여부 먼저 확인). - 시간·거리 전용 종목의 보드 저장 구멍(설계 판단 대기): 컴포저 계약이 reps>0 세트만 직렬화해 러닝 등은
sets:[]로 나가고 서버가 저장 전체를 22023 거부("입력값을 확인해 주세요"). 이슈 #831에 기록.
5. 적용 결과
| 항목 | 결과 |
|---|---|
| 자유 기록 보드 저장 | 서버 22023 거부 → 저장 성공·수정 재진입 복원 (pgTAP 13·유닛 3·Production 검증기 프로브 에코 실측) |
| 컴포저 종목 검색 공급 | {id,name} 축약 → 운동 플로우와 동일 exerciseCatalog(synonym·기록 프로필·커스텀 등록) |
| 컴포저 콜드 진입 스타일 | 워밍 경합 의존 → lazy 쌍 workout.css 동반 (배포 mobileRoot 의존 배열에서 GroupComposer↔workout css 동반 확인) |
| 탭바 숨김 누출 | 숨은 판 마커가 전 탭 탭바 소거 가능 → 배포 CSS [data-lg-view^=workout\.]:not(.tab-pane.off *) 실측 |
| 보드 서체 | 네이버 CDN @import → 셀프호스팅 woff2 2종, 배포 group-*.css pstatic 참조 0·/fonts/NanumSquareRound{B,EB}.woff2 200 실측 |
| % 입력 시각 상태 | 기준 1RM 미선택 시 kg 오표시 → pctSel 일치(저장 수학 게이트 pctMode 불변) — 배포 workout-*.css 신 모달 규칙·구 드롭다운 규칙 소거 실측 |
| CI | #832~#835 verify 전부 green, migration-smoke 8m58s(pgTAP 포함) |
미검증: 오너 실기기(컴포저 왕복 실사용·% 픽커 모달·보드 서체 실렌더·탭바 거동) — 확인 대기.
6. 이번 개선으로 향상된 것
화이트보드 작성이 곧 계획 작성이다
그룹 멤버가 이미 아는 편집기(종목 카드·세트 드로어·검색/커스텀/복합/자유기록 픽커)가 보드에서 그대로 — 별도 학습 없는 단일 문법.
자유 기록이 보드의 정식 시민이 됐다
kind:"note"가 서버 계약·읽기/쓰기 왕복·테스트 3겹에 정식 편입 — 격하·유실 없이 남는다.
구조적으로 남는 것
- 보드 exercises 스냅샷 계약에 kind 분기(검증기 + group-props.md §5) — 이후 항목 유형 추가의 선례
- keep-alive 판 안에서 발화하는 마커의 탭바 규칙 문법(
:not(.tab-pane.off *)) — 다음 판-내-전면-화면이 같은 함정을 밟지 않는다 - 보드 서체까지 포함해 모바일 앱 화면의 CDN 런타임 의존 0 유지
남은 것
- 오너 실기기 확인(위 미검증 4종)
- 시간·거리 전용 종목 보드 저장 구멍 — 설계 판단(이슈 #831)
Dovemayo_gothic.woff2삭제 여부 — 되돌릴 여지로 유지 중