출시 전후 정리 묶음 — 감사 확정 중·저위험 결함 8건 일괄 수리 (2026-09-01)
- 기간: 2026-09-01 (1세션, 오너: "#928좀 진행해줄래?")
- 랜딩: PR #1049(Phase 1~4 한 묶음,
7fd716e6) — 마이그레이션 0, 클라이언트 전용(Vercel 자동 배포) - 설계서: 없음(수리 묶음 — 분석·Phase 계획·예상 효과는 이슈 #928 스레드)
- 정본:
groupStore.ts(목록 오류 재시도·월 마크 병합·좋아요 스탬프) ·SetComposer(복합 동작별 편집) ·pendingWorkoutSavesStore.ts(플러시 통계 재확인) ·profileController.ts(계정 삭제 로컬 정리) - 도구: 없음(코드 읽기 + 스토어 행동 테스트로 재현)
- 게이트:
tests/react/issue928Cleanup.test.mjs10건(스토어 행동·렌더 계약·배선 앵커) + 기존 대기 큐 테스트 2파일 보강 - 버그리포트: 없음 — 감사(워크플로
wf_e7702b27-b0d, 2026-08-30) 발견 묶음이라 이슈 #928 표가 정본 - 계약:
designContract.ts액션 마커group.list.retry등재
한 줄 요약 — 출시 전 전면 감사가 확정한 중·저위험 결함 11건을 전수 재조사해, 이후 트랙이 이미 수리한 3건(①⑥⑨ — #975·#950)을 제외한 8건을 한 PR로 수리했다. 서버 변경 0.
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 0 | 11건 전수 재조사 — 기수리 3건 판별, 유효 8건 원인 지목 | ✅ 이슈 스레드 |
| Phase 1 | 그룹 탭 ③④⑧ (목록 오류·달력 마크·좋아요 경합) | ✅ PR #1049 |
| Phase 2 | 기록·세션 상세 ②⑦⑩ (시간 버튼·복합 수정·드래그 취소) | ✅ PR #1049 |
| Phase 3 | 동기화·계정 ⑤⑪ (플러시 통계 재확인·계정 삭제 로컬 정리) | ✅ PR #1049 |
| Phase 4 | 검증·랜딩 (신규 테스트 10건, 로컬 게이트 전 체인, CI 1회) | ✅ PR #1049 |
1. 배경
출시 전 전면 감사(2026-08-30, 워크플로 wf_e7702b27-b0d)가 개별 트랙을 열 규모는 아니지만 사용자가 실제로 겪는 오동작 11건을 이슈 #928로 묶어 두었다. 오너 go(09-01)로 착수. 재조사 결과 3건은 그 사이 #975(입력 검증 정비)·#950(입력 상한 정비)이 이미 수리해 두었다 — 감사 목록은 착수 시점에 반드시 최신 코드로 전수 재확인해야 한다는 것이 이 트랙의 첫 결론.
2. 문제 제기
③ 그룹 목록 첫 로드 실패가 가짜 "아직 그룹이 없어요"로 영구 고정
실패해도 화면은 오류 표시 없이 빈 목록을 그렸고(mobileApp.tsx — error 상태 분기 없음), 탭 재진입도 "이미 로드했다" 기록(loadedOwner 래치) 때문에 재요청하지 않았다.
④ 미니 달력에서 월을 넘기면 보드 표시(워시 마크)가 전부 소실
마크 원천(board_dates)을 서버가 "보고 있는 날짜의 달" 창으로만 보내는데, 클라이언트가 다른 달을 가져오는 경로가 없었다. 서버 함수는 이미 기간 인자(p_from/p_to, ≤62일)를 받고 있었다.
⑧ 좋아요가 눌렀다 되돌아간 것처럼 보임
보드 재진입 시 백그라운드 새로고침(swr) 응답(누르기 전 값)이 방금 누른 좋아요를 무조건 덮었다(groupStore.ts loadBoard 성공 핸들러).
② 타인 기록에도 "시간 수정" 버튼 노출 — 저장해도 무동작
버튼 노출 조건(SessionSheetBody.tsx)에 "내 세션인가" 판정이 없었다.
⑦ 복합종목 세트 수정이 조용히 버려짐
세트 수정 모달이 복합종목에도 합계 횟수 한 칸만 열었는데, 저장·표시는 동작별 값(scheme)을 우선하므로 고친 합계가 어디에도 반영되지 않았다.
⑩ 취소된 드래그 뒤 리스너 누수 + 엉뚱한 세트 추가 창
세트 순서 드래그가 window 리스너를 pointerup에서만 제거해, 브라우저가 드래그를 강제 취소(pointercancel)하면 리스너가 남아 다음 탭이 엉뚱한 위치에 컴포저를 열었다.
⑤ 오프라인 대기 기록 동기화 직후 하루 통계가 옛값 (#902 동종)
온라인 저장·삭제 경로는 저장 영수증으로 통계 반영 여부를 재확인(reconcileCompletedWorkoutReadModels)하는데, 오프라인 큐를 올리는 동기화(flush) 경로에만 이 호출이 빠져 있었다.
⑪ 계정 삭제 후 운동 기록이 기기에 무기한 잔존 (개인정보)
삭제 시 로컬 정리가 화면 스냅샷 1종만 지웠다. 진행중 운동 초안(봉투)·보관함(#887)·오프라인 대기 큐는 IndexedDB에 남았다.
3. 해결 방안
원칙 (오너 결정)
- D1 (2026-09-01, go에 포함): 계정 삭제 시 이 기기의 로컬 운동 기록(진행중 초안·보관함·오프라인 대기 큐)을 함께 삭제한다. 로그아웃은 종전대로 보존(재로그인 시 이어짐).
접근
- 한 묶음 PR·CI 1회(전역 규칙) — 전부 클라이언트 수정이라 마이그레이션 잠금(§15) 불요.
- ④는 서버 RPC 신설 대신 기존 기간 인자 활용(서버 변경 0)을 채택. 전용 마크 RPC 신설은 기각 — 월 이동은 드문 조작이라 보드 payload 재사용으로 충분.
- ⑧은 재검증 자체를 취소(tokens.board 상승)하는 안을 기각 — 보드·댓글 갱신까지 버리게 된다. likes만 스탬프로 보호.
4. 적용한 내용
Phase 1 — 그룹 탭 (③④⑧)
groupStore.ts:activateForGroupTab이 error 상태면 재진입마다 재요청 /loadBoardMonthMarks(year, monthIndex)신설(월당 1회, 월 창 단위 병합mergeBoardDatesForMonth— 날짜 이동·캐시 히트가 타 달 마크를 지우지 않음) /likeStamp+likePending으로 swr 응답의 likes 덮어쓰기 차단.GroupScreen.tsx: 목록 오류 화면(문구+group.list.retry버튼, 목록이 있으면 오류 화면 미전환) /GpCalSheet에onMonth콜백.mobileApp.tsx배선,group.css재시도 버튼,designContract.ts마커 등록.
Phase 2 — 기록·세션 상세 (②⑦⑩)
SessionSheetBody.tsx: 시간 수정 버튼 노출 조건에onUpdateSession && !(profile.self === false).WorkoutRecordParts.tsxSetComposer: scheme 복합 세트는 동작별 횟수 칸(빠른 추가 컴포저와 같은 문법)으로{scheme, repsParts, reps=합}커밋.WorkoutRecord.tsx: 드래그 리스너에 pointercancel 추가 — 정리만 하고 컴포저 미발동.
Phase 3 — 동기화·계정 (⑤⑪)
pendingWorkoutSavesStore/-Controller+appController.tsx: 플러시 성공 시 업로드 영수증마다reconcileCompletedWorkoutReadModels(receipt, [date])호출. 통계 복구 컨트롤러 조립을 대기 저장 컨트롤러 앞으로 이동(내용 불변).workoutDraftCache.tspurgeWorkoutDraftDataForUser(봉투+보관함, 소유자 스코프) ·pendingWorkoutSaves.tspurgePendingWorkoutSavesForUser신설,profileController.deleteAccount에서만 호출(로그아웃 경로 미접촉).
주요 결정과 그 근거
- 마크는 상태 신설 없이 기존
boardDates에 병합 — 화면 계약 불변, 소비처 수정 0. - 계정 삭제 정리는 서버 삭제 성공 뒤 실행하고 실패해도 삭제 성공을 되돌리지 않음(#663 계약 유지).
작업 중 드러난 것
- 감사 목록의 3건은 착수 시점에 이미 수리돼 있었다(#975 Phase 3-1·3-4, #950) — 감사·백로그 묶음은 반드시 현재 코드로 전수 재확인 후 착수.
tests/audit/pending-changes.json은 명부(manifest)에 없는 신규 테스트 파일을 선언하면 오히려 게이트가 깨진다 — 선언은 명부 등재 파일 전용.- PowerShell로 한글 포함 파일을
Get-Content→Set-Content로 고치면 인코딩이 깨진다(CP949 오독+BOM) — 다중행·한글 파일 수정은 Edit 도구로만.
5. 적용 결과
| 항목 | 전 → 후 |
|---|---|
| 그룹 목록 로드 실패 시 | 가짜 "그룹 없음" 영구 고정 → 오류 문구+재시도 버튼, 재진입 자동 재요청 (행동 테스트) |
| 달력 이전/다음 달 보드 마크 | 0개 표시 → 그 달 실제 보드 날짜 표시·월당 1회 로드 (행동 테스트) |
| 보드 재진입 직후 좋아요 | 재검증 응답이 덮어 깜빡 복귀 → 유지, 서버 집계로 화해 (경합 2경로 테스트) |
| 타인 세션 시간 수정 버튼 | 노출(저장 무동작) → 비노출 (렌더 계약 테스트) |
| 복합 세트 수정 모달 | 합계 1칸(저장 시 버려짐) → 동작별 칸(scheme 커밋) (렌더 계약 테스트) |
| pointercancel 후 다음 탭 | 엉뚱한 컴포저+리스너 누수 → 정리·미발동 (배선 앵커) |
| 동기화 직후 하루 통계 | 재확인 없음(옛값 고착 가능) → 영수증마다 재검증 호출 (행동 테스트) |
| 계정 삭제 후 로컬 잔존 | 초안·보관함·대기 큐 잔존 → 소유자 것 전량 삭제, 타 소유자 보존 (행동 테스트) |
| 게이트 | 로컬 check 전 체인 + 전체 테스트 2,281건 green, CI 1회 green |
| 미검증 | ⑦ 모달의 시각 품질, ⑧ 실기기 체감 — 자동 증거 범위 밖(§14에 따라 게이트 아님) |
6. 이번 개선으로 향상된 것
통신이 나쁜 곳에서도 그룹 탭이 스스로 복구된다
실패가 보이고(문구), 사용자가 고칠 수 있고(재시도), 재진입만 해도 다시 시도한다.
복합종목 수정이 "한 일이 사라지는" 부류에서 벗어났다
수정 모달에서 고친 값이 실제 저장·표시에 반영된다 — 조용한 무반영은 데이터 불신으로 직결되는 부류였다.
구조적으로 남는 것
- 오프라인 큐 동기화가 온라인 저장 경로와 같은 통계 재확인 문법을 공유 — 경로별 편차 1건 해소.
- 계정 삭제의 로컬 정리 범위가 "소유자 데이터 전부"로 정의되고 서비스 함수 2종으로 남음.
- 달력 마크 병합 규칙(월 창 단위)과 좋아요 스탬프가 회귀 테스트로 상주.
남은 것
- 이종 복합(동작별 원자, parts형) 세트의 수정 모달은 이번 범위 밖 — 같은 모양의 문제가 있는지 별도 확인 가치(감사 항목은 scheme형만 지목).