Skip to content

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.mjs v78 계약 갱신(→ 프리픽스 재발 차단 doesNotMatch 포함)
  • 버그리포트: 없음(기능 배선 트랙 — 오너 보고 결함 아님)
  • 계약: group-props.md §5(컴포저 = 계획 편집기 재사용·kind:"note" 왕복) · §4(보드 서체 셀프호스팅) · group_boards 스냅샷 코멘트(kind 편입)

Phase 현황

Phase내용상태
Phase 0v78 반입(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 4Production 실측(청크 마커·폰트 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 삭제 여부 — 되돌릴 여지로 유지 중