Skip to content

운동 초안 보호

한국어 번역본

이 문서는 원문(영어)의 한국어 번역이다. 정본은 원문이며, 계약·게이트 판단이 갈리면 원문을 따른다. 원문: docs/data/workout-draft-protection.md

범위

완료되지 않은 운동은 토큰 만료, 앱 리로드, 일시적 네트워크 장애를 계정 경계를 넘지 않고 견뎌내야 한다. 이 정책은 진행 중인 운동 기록 스냅샷에 적용된다. 계획 세션 편집은 별개의 플로우로 남는다.

운동 날짜의 경계 (#1529, v0.17.10)

운동 날짜는 일지의 선택 날짜와 독립된 초안의 값이다. 메인·그룹 보드에서 새 운동을 시작하면 시작 시점의 오늘을 draft.date로 확정한다. 일지에서 과거 기록·계획을 추가할 때는 작성 시작 시 선택 날짜를 한 번 전달하고, 기존 기록 수정은 원본의 날짜를 유지한다. 보드의 출처 날짜는 출처 정보이며 운동 날짜를 대신하지 않는다.

화면 어댑터가 운동 초안을 구성할 때 이 날짜를 보존한다. 자동·명시적 저장, 기기 초안 봉투, 서버 체크포인트, 계정 전환 시 보존, 이어하기와 앱 리로드 복원은 모두 같은 운동 날짜를 유지한다. 복원은 저장된 초안/봉투의 날짜를 쓰며 일지 선택 날짜를 쓰지 않는다. 자정을 넘어도 기존 운동의 날짜를 다시 정하지 않는다.

일지의 날짜 선택·월 이동은 일지 조회 상태에만 영향을 주며, 진행 중 초안이나 다른 탭의 날짜를 바꿀 수 없다. 운동 시작·이어하기도 일지 선택 날짜를 초기화하지 않는다. 진입 경로별 날짜 원천은 달력 날짜 경계를 따른다. 앱 PR #1539, 4d9705c6으로 2026-09-10 release/v0.17.10에 반영됐다. Production 배포는 포함하지 않으며 적용 기록에서 검증·release 병합·배포를 구분한다.

소유권

영속되는 모든 초안은 비어 있지 않은 Supabase 사용자 ID를 가진다. IndexedDB v3는 소유자 범위 키를 사용하므로, 다른 계정으로 로그인해도 이전 계정의 초안을 읽지도 덮어쓰지도 않는다. localStorage 미러나 이중 읽기 경로는 없다.

앱 컨트롤러는 두 정체성을 독립적으로 추적한다:

  • 현재 인증된 사용자
  • 메모리 안 운동 스냅샷의 소유자

인증이 바뀌면 최신 스냅샷을 먼저 이전 소유자 몫으로 저장한 뒤, 메모리 안 화면 상태에서 제거한다. 복원은 로컬 초안 디바운스를 플러시하고 새로 인증된 사용자의 키만 읽는다. 정체성 전환이 진행되는 동안 이전 화면 세대에서 온 콜백은 무시한다.

소유자가 없는 초안은 거부한다. 현재 v3 엔벌로프만 읽고 쓰며, 유효하지 않거나 만료된 형태는 추측해서 활성 에디터에 밀어 넣지 않고 제거한다.

수명

보존 기간은 마지막으로 성공한 로컬 초안 쓰기로부터 7일이다. 편집할 때마다 savedAtexpiresAt이 갱신된다. 만료 경계 시점 또는 그 이후에 로드하면 해당 사용자의 만료된 슬롯만 삭제하고 초안은 반환하지 않는다.

편집은 250ms 디바운스와 마지막 쓰기 우선(last-writer-wins)을 쓴다. 일치하는 폐기나 서버 저장 확정은 슬롯을 삭제하기 전에 먼저 대기 중인 디바운스 작업을 취소하고 현재 로컬 쓰기를 기다린다. 탭 간 잠금이나 compare-and-swap 루프는 쓰지 않는다.

고정된 기간은 방치되었거나 민감한 운동 세부 정보가 브라우저 저장소에 무기한 남는 것을 막으면서도, 통상적인 세션·연결 복구에는 충분한 시간을 남긴다.

삭제

초안은 다음 경우에만 삭제된다:

  • 사용자가 진행 중인 운동을 명시적으로 폐기한다
  • 해당 운동이 서버에서 저장 확정된다
  • 7일 보존 기간이 만료된다

서버 쓰기가 실패해도 초안을 삭제하지 않는다.

저장소 경계

이것은 브라우저 프로필 수준의 보호이며, 같은 OS/브라우저 프로필을 이미 장악한 사람에 대한 암호화가 아니다. 네이티브 보안 토큰 저장소는 운동 내용 저장소와 분리된 채로 유지된다.