diff --git a/myApp/HaruDanim/CLAUDE.md b/myApp/HaruDanim/CLAUDE.md index 1cecd88..38a3825 100644 --- a/myApp/HaruDanim/CLAUDE.md +++ b/myApp/HaruDanim/CLAUDE.md @@ -19,7 +19,7 @@ | 번들 ID | `com.yechan.HaruDanim` / App Group `group.com.yechan.HaruDanim` / CloudKit `iCloud.com.yechan.HaruDanim` | | 수익 모델 | 부분 유료화. 무료 한도(행동 10·목표 2·목표당 다짐 3) + 프리미엄 결제 시 해제 | | 결제 상품 | `com.yechan.HaruDanim.premium.monthly`(월 4,400원) / `.yearly`(년 44,000원) / `.lifetime`(77,000원) — **ASC 등록·판매 중(실제 가격, 2026-07-28)**. 로컬 테스트 구성(HaruDanim.storekit)도 동일 가격. 가격은 코드에 없고 `product.displayPrice`로 동적 표기, 년 구독 절약률 배지도 동적 계산 | -| 현재 상태 (2026-08-11) | **버전 1.4 개발 완료 — 제출 대기(사용자 실기기 테스트 후 업로드 예정)**. 1.4 내용(사용자 A안 승인, 2026-08-11 세션에서 전부 구현·검증): ①**iOS 18.0 하한 인하**(위 플랫폼 행 — 글라스 버블 머티리얼 폴백·사용자 심볼 안전 렌더 `Image(safeSymbol:)`[iOS 55곳+워치 7곳, 18에 없는 카탈로그 심볼 3개 물음표 폴백]·`-symbolAuditDump`. ⚠️하한 18 Release(-O wholemodule)에서 swift-frontend가 제네릭 중첩 Coordinator의 deinit 최적화 중 크래시하는 컴파일러 버그 실측 — DiaryZoomCoordinator를 비제네릭 톱레벨+AnyView 소거로 우회, 되돌리지 말 것) ②**펜슬 저장 디바운스**(§6.7 — 획마다 전체 직렬화·블랍 재기록하던 버벅임 해소) ③**통계 '오늘 기준' 롤링**(§6.6·§10 ⑤ — 지난 7일/30일) ④**CSV 구간 내보내기**(§6.8, 코어 CSVExport 추출) ⑤**일기 내보내기 후 삭제**(§6.7, 렌더 코어 DiaryExportRenderer 추출) ⑥**데이터 정리·초기화**(§6.8 — 전체/처음~날짜까지+백업 3종[CSV·일기 PDF·정리 리포트 ArchiveReport]) ⑦도움말 재구성(1.4(2) 검수 후속, 2026-08-12 — 뭉친 주제 3곳 분리: 기록·통계 그룹에 '지난 7일·30일로 보기'·'데이터 정리·초기화' 독립 주제 신설[CSV 주제는 백업·복원만, 비교 주제는 1.3 원문 복귀], 일기 그룹 '펜슬 노트 페이지'에서 '사진·도형·텍스트 꾸미기' 분리. 이미지는 그룹당 대표 1주제 원칙 유지 — 18장 전수 실사로 참조·언어 매칭 무결 확인)·l10n(전 카탈로그 missing/stale 0)·**워치 실행 햅틱 .click→.success 상향**(1.4(2) — 실사용 '너무 약함' 보고)·MARKETING_VERSION 1.4/**빌드 2**(TestFlight 재업로드용 — 같은 버전은 빌드 번호만 올려 업로드하면 1.4(2)로 기존 테스터에 배포됨)·`Marketing/AppStore/whats-new-1.4.txt`. 검증: 자가 검증 `-progressSelfTest` 49·`-csvImportTest` 20·`-intentSmokeTest` 10·`-dataResetTest` 18 ALL PASS(26 심+18.5 심), Debug/Store/워치 빌드 성공, 18.5·26 시각 QA(버블 폴백·일기 줌·롤링 통계·초기화 화면·리포트 PDF 3페이지 실물). **스토어 제출 시**: 하한 인하는 ASC에서 바꿀 것 없음(바이너리에서 자동 파생), 스크린샷 재촬영 불필요(통계 토글·설정 행 추가는 사소 — 교체 여부는 사용자 판단), 심사 특이사항 없음. **버전 1.3 App Store 출시 2026-08-06** — 3일 실사용+심층 검증(2026-08-05: 자가 검증 3종 ALL PASS·벌크 1만 건·일기 내보내기 일치·위젯 스모크·만료 열람·도움말 — 회귀 0) 통과 후 제출. 심층 검증에서 기록한 사소 잔여 엣지 9건은 후속 후보로만 보류(전부 극단 한정·자가 복구형, 필기 blob 동시 편집 한계는 §6.7). 1.3 내용: 아래 소기능 이력의 일기 개선 묶음(양식 펜 색 고정 + 도형·텍스트 상자 편집 개편 + 검토 후속 수정), `Marketing/AppStore/whats-new-1.3.txt`. 잔여 엣지 9건 상세: 최소 크기 커밋 스냅백·일기 상세 이탈 시 빈 텍스트 상자 잔존·페이지 가장자리 손잡이 클리핑·인라인 편집 중 페이지 스와이프 상태 잔존·회전 상자 커서 어색함 등(1.0부터의 구조 한계인 필기 blob 동시 편집 덮어쓰기·열린 캔버스 원격 미갱신은 §6.7, 같은 페이지 동시 필기 회피 권장). 1.2 내용: 주기 몫 완료 상태 표기(§4.2)+도움말 '진행률 읽는 법' 갱신(상세는 아래 소기능 이력). **버전 1.1 출시 2026-07-30.** 1.1 내용: ①**맥 일기 개방**(§6.7 — 사이드바 노출 + '그리기' 토글로 마우스 필기, 텍스트 우선 도구줄. 아이패드 경로 불변. 맥 실기 확인 완료) ②**CSV 가져오기**(§6.8 — 프리미엄, 추가 전용·미리 보기·멱등. 검증 `-csvImportTest` 20건 ALL PASS) ③**일기 기본 표시 설정**(§6.7 — 캘린더·목표·행동 전 날짜 기본값 + 날짜별 재정의, 스키마 무변경 마커 방식) ④**'짧은 기록 무시' iCloud KVS 동기화**(§6.8 — 전 기기 공통) ⑤스크린샷 전면 개편(아이폰 10·아이패드 10·워치 합성 1 × ko/en/ja — ASC 업로드 완료) ⑥스토어 메타데이터 변경(앱 이름·부제목·키워드·프로모션 — 검색·색인 강화, `Marketing/AppStore/app-name-subtitle.txt`·`keywords.txt`·`promotional-text.txt`. 스토어 표기만 변경, 설치 이름 불변) ⑦MARKETING_VERSION 1.1. **버전 1.0 출시 2026-07-28.** 이후 작업은 사용자 실사용 중 발견한 버그 수정·사용자가 원하는 소기능 추가만 한다(선제적 기능 제안·대규모 리팩터링 금지). 출시 후 소기능 이력: **일기 도형·텍스트 상자 UX 2차 개편**(2026-08-03, 사용자 요청 — §6.7. 종류별 모양 손잡이[사각형 꼭짓점 4개·원 상하좌우 4개·선/화살표 양 끝점]와 정사각형·정원 스냅 피드백, 텍스트 상자 제자리 입력[탭→바로 키보드]·테두리 색 독립, '글 수정'→'스타일' 시트로 재편, borderColorHex 필드 추가. 1.3으로 버전 상향)·**일기 도형·텍스트 상자 편집 강화**(2026-08-03, 사용자 요청 — §6.7. "정해진 모양·크기 그대로만 나온다"는 보고: 크기 손잡이(가로세로 자유·맥 마우스 대응)·회전 제스처·'도형 수정' 시트[색·선 굵기·채우기·모서리 둥글기·회전]·텍스트 시트 확장[글자 크기·굵게·정렬·테두리], DiaryPageItem에 기본값 필드 8개 추가)·**양식 페이지 펜 색 고정**(2026-08-03, 사용자 요청 — §6.7. 다크/라이트 잉크 반전으로 맥·아이패드에서 필기가 안 보이던 실사용 보고 해소, 내보내기 라이트 트레잇 고정 포함)·**주기 몫 완료 상태 표기**(2026-08-01, 사용자 요청 — §4.2. 도움말 '진행률 읽는 법' 문구도 갱신. 같은 날 MARKETING_VERSION 1.2(빌드 1)로 상향 + `Marketing/AppStore/whats-new-1.2.txt` 작성·TestFlight 업로드, **심사 통과 → App Store 출시 완료(2026-08-02, 사용자 확인)**). 업데이트 제출: 버전 올려 빌드 업로드 → 심사 제출(IAP는 첫 심사에서 승인됐으므로 첨부 불필요). 출시 전 이력 — RC는 커밋 `f05e611`(버전 1.0 (1)), 이후 실사용 보고 기반 수정 라운드(전부 커밋·푸시·실기기 확인 완료): l10n 누락 일괄·ja 앱 이름 'Haru Danim' 통일·워치 컴플리케이션 추천 캡 제거(§11)·⑥ 위젯 종료 버튼(§10)·진행률 환산 캡+수행일 집계+수행일 아님 표기(§4.2)·파일명/CSV 헤더 현지화(§7)·워치 컴플리케이션 목표 필터(§6.4·§11, 사용자 승인 소기능)·기록 시각 입력 상시 휠 다이얼+분 절사(§6.5)·일기 달력 기분 사진 백레이어(§6.7)·다짐 없는 목표 '다짐 없음' 표기(§4.3)·워치 측정 중 행 탭 종료(§11)·도움말 예시 스크린샷 18장(§6.8)·설정 하루 시작 시간 접힘 행(§6.5)·**모음 탭 즐겨찾기 모아 보기**(§6.1 — 행동 많아질 때 혼잡도 대응, 사용자 승인 소기능. v1 별도 카드 가로 줄[대안 A~F 중 A]이 "따로 노는 느낌" 실사용 피드백으로 v2 그리드 섹션 통합+나머지 접기로 교체, 같은 날 후속 피드백으로 편집 모드 섹션 인지·섹션 간 드래그 지정/해제·"지정 순서대로 뒤에" 규칙 추가, 2026-07-22)·**en 복수형 정리**(2026-07-22 — 수량 1일 때 "1 times/quests" 류 오류를 카탈로그 plural variation으로 14건 전환[iOS 10·위젯 3·워치 1], 모음 탭 섹션 제목 en "Favorite"→"Favorites"로 워치와 통일. ko/ja 무영향). **App Store 등록 자료는 `Marketing/`에 완비**(설명·키워드·구독 메타·스크린샷 원본/합성판, README에 재생성 절차 — 앱 변경이 자료에 영향 주면 요청 없이 함께 갱신할 것. 2026-07-22 즐겨찾기 섹션(v2) 반영해 아이폰·아이패드 01-main과 help-main ko/en/ja 재촬영·합성판 재생성. **2026-07-29 스크린샷 세트 확장** — 아이폰 8장(+위젯·버블 메뉴·iCloud 동기화 합성·다크 모드)/아이패드 7장(+주간 타임테이블·위젯·동기화 합성) × ko/en/ja, 촬영·합성 절차는 Marketing/README '추가 스크린샷' 절. **1.1 제출 때 ASC 업로드 완료**). **사업자 등록·통신판매업 신고 완료(2026-07-22)** — 서류 절차 끝, ASC 등록·유료 계약 진행 가능(순서는 Marketing/README 체크리스트). 사용자는 계속 실사용 테스트 중이며 버그 보고 시: 최소 변경 + 시뮬레이터 재검증 + l10n 루틴(§2.3) + Debug/Store(워치 파일이면 워치까지) 빌드 + 커밋·푸시 | +| 현재 상태 (2026-08-11) | **버전 1.4 개발 완료 — 제출 대기(사용자 실기기 테스트 후 업로드 예정)**. 1.4 내용(사용자 A안 승인, 2026-08-11 세션에서 전부 구현·검증): ①**iOS 18.0 하한 인하**(위 플랫폼 행 — 글라스 버블 머티리얼 폴백·사용자 심볼 안전 렌더 `Image(safeSymbol:)`[iOS 55곳+워치 7곳, 18에 없는 카탈로그 심볼 3개 물음표 폴백]·`-symbolAuditDump`. ⚠️하한 18 Release(-O wholemodule)에서 swift-frontend가 제네릭 중첩 Coordinator의 deinit 최적화 중 크래시하는 컴파일러 버그 실측 — DiaryZoomCoordinator를 비제네릭 톱레벨+AnyView 소거로 우회, 되돌리지 말 것) ②**펜슬 저장 디바운스**(§6.7 — 획마다 전체 직렬화·블랍 재기록하던 버벅임 해소) ③**통계 '오늘 기준' 롤링**(§6.6·§10 ⑤ — 지난 7일/30일) ④**CSV 구간 내보내기**(§6.8, 코어 CSVExport 추출) ⑤**일기 내보내기 후 삭제**(§6.7, 렌더 코어 DiaryExportRenderer 추출) ⑥**데이터 정리·초기화**(§6.8 — 전체/처음~날짜까지+백업 3종[CSV·일기 PDF·정리 리포트 ArchiveReport]+**'일기만' 대상 축**[1.4(3), 2026-08-12 — 기록·구조·양식 보존, 백업은 일기 PDF만, `DataReset.deleteDiaries`·`-dataResetScope diary`·자가 검증 27건으로 확장]) ⑧**라디얼 모드 FAB 가림 수정**(1.4(3) — 목록 마지막 행이 FAB에 가려지던 실측 문제: 각 탭 루트에 하단 safeAreaInset 78pt, ⚠️NavigationStack '안쪽'에 붙일 것[바깥은 TabView가 safe area 재설정해 무시됨, 실측]) ⑦도움말 재구성(1.4(2) 검수 후속, 2026-08-12 — 뭉친 주제 3곳 분리: 기록·통계 그룹에 '지난 7일·30일로 보기'·'데이터 정리·초기화' 독립 주제 신설[CSV 주제는 백업·복원만, 비교 주제는 1.3 원문 복귀], 일기 그룹 '펜슬 노트 페이지'에서 '사진·도형·텍스트 꾸미기' 분리. 이미지는 그룹당 대표 1주제 원칙 유지 — 18장 전수 실사로 참조·언어 매칭 무결 확인)·l10n(전 카탈로그 missing/stale 0)·**워치 실행 햅틱 .click→.success 상향**(1.4(2) — 실사용 '너무 약함' 보고)·MARKETING_VERSION 1.4/**빌드 3**(TestFlight 재업로드용 — 같은 버전은 빌드 번호만 올려 업로드하면 1.4(2)로 기존 테스터에 배포됨)·`Marketing/AppStore/whats-new-1.4.txt`. 검증: 자가 검증 `-progressSelfTest` 49·`-csvImportTest` 20·`-intentSmokeTest` 10·`-dataResetTest` 27 ALL PASS(26 심+18.5 심), Debug/Store/워치 빌드 성공, 18.5·26 시각 QA(버블 폴백·일기 줌·롤링 통계·초기화 화면·리포트 PDF 3페이지 실물). **스토어 제출 시**: 하한 인하는 ASC에서 바꿀 것 없음(바이너리에서 자동 파생), 스크린샷 재촬영 불필요(통계 토글·설정 행 추가는 사소 — 교체 여부는 사용자 판단), 심사 특이사항 없음. **버전 1.3 App Store 출시 2026-08-06** — 3일 실사용+심층 검증(2026-08-05: 자가 검증 3종 ALL PASS·벌크 1만 건·일기 내보내기 일치·위젯 스모크·만료 열람·도움말 — 회귀 0) 통과 후 제출. 심층 검증에서 기록한 사소 잔여 엣지 9건은 후속 후보로만 보류(전부 극단 한정·자가 복구형, 필기 blob 동시 편집 한계는 §6.7). 1.3 내용: 아래 소기능 이력의 일기 개선 묶음(양식 펜 색 고정 + 도형·텍스트 상자 편집 개편 + 검토 후속 수정), `Marketing/AppStore/whats-new-1.3.txt`. 잔여 엣지 9건 상세: 최소 크기 커밋 스냅백·일기 상세 이탈 시 빈 텍스트 상자 잔존·페이지 가장자리 손잡이 클리핑·인라인 편집 중 페이지 스와이프 상태 잔존·회전 상자 커서 어색함 등(1.0부터의 구조 한계인 필기 blob 동시 편집 덮어쓰기·열린 캔버스 원격 미갱신은 §6.7, 같은 페이지 동시 필기 회피 권장). 1.2 내용: 주기 몫 완료 상태 표기(§4.2)+도움말 '진행률 읽는 법' 갱신(상세는 아래 소기능 이력). **버전 1.1 출시 2026-07-30.** 1.1 내용: ①**맥 일기 개방**(§6.7 — 사이드바 노출 + '그리기' 토글로 마우스 필기, 텍스트 우선 도구줄. 아이패드 경로 불변. 맥 실기 확인 완료) ②**CSV 가져오기**(§6.8 — 프리미엄, 추가 전용·미리 보기·멱등. 검증 `-csvImportTest` 20건 ALL PASS) ③**일기 기본 표시 설정**(§6.7 — 캘린더·목표·행동 전 날짜 기본값 + 날짜별 재정의, 스키마 무변경 마커 방식) ④**'짧은 기록 무시' iCloud KVS 동기화**(§6.8 — 전 기기 공통) ⑤스크린샷 전면 개편(아이폰 10·아이패드 10·워치 합성 1 × ko/en/ja — ASC 업로드 완료) ⑥스토어 메타데이터 변경(앱 이름·부제목·키워드·프로모션 — 검색·색인 강화, `Marketing/AppStore/app-name-subtitle.txt`·`keywords.txt`·`promotional-text.txt`. 스토어 표기만 변경, 설치 이름 불변) ⑦MARKETING_VERSION 1.1. **버전 1.0 출시 2026-07-28.** 이후 작업은 사용자 실사용 중 발견한 버그 수정·사용자가 원하는 소기능 추가만 한다(선제적 기능 제안·대규모 리팩터링 금지). 출시 후 소기능 이력: **일기 도형·텍스트 상자 UX 2차 개편**(2026-08-03, 사용자 요청 — §6.7. 종류별 모양 손잡이[사각형 꼭짓점 4개·원 상하좌우 4개·선/화살표 양 끝점]와 정사각형·정원 스냅 피드백, 텍스트 상자 제자리 입력[탭→바로 키보드]·테두리 색 독립, '글 수정'→'스타일' 시트로 재편, borderColorHex 필드 추가. 1.3으로 버전 상향)·**일기 도형·텍스트 상자 편집 강화**(2026-08-03, 사용자 요청 — §6.7. "정해진 모양·크기 그대로만 나온다"는 보고: 크기 손잡이(가로세로 자유·맥 마우스 대응)·회전 제스처·'도형 수정' 시트[색·선 굵기·채우기·모서리 둥글기·회전]·텍스트 시트 확장[글자 크기·굵게·정렬·테두리], DiaryPageItem에 기본값 필드 8개 추가)·**양식 페이지 펜 색 고정**(2026-08-03, 사용자 요청 — §6.7. 다크/라이트 잉크 반전으로 맥·아이패드에서 필기가 안 보이던 실사용 보고 해소, 내보내기 라이트 트레잇 고정 포함)·**주기 몫 완료 상태 표기**(2026-08-01, 사용자 요청 — §4.2. 도움말 '진행률 읽는 법' 문구도 갱신. 같은 날 MARKETING_VERSION 1.2(빌드 1)로 상향 + `Marketing/AppStore/whats-new-1.2.txt` 작성·TestFlight 업로드, **심사 통과 → App Store 출시 완료(2026-08-02, 사용자 확인)**). 업데이트 제출: 버전 올려 빌드 업로드 → 심사 제출(IAP는 첫 심사에서 승인됐으므로 첨부 불필요). 출시 전 이력 — RC는 커밋 `f05e611`(버전 1.0 (1)), 이후 실사용 보고 기반 수정 라운드(전부 커밋·푸시·실기기 확인 완료): l10n 누락 일괄·ja 앱 이름 'Haru Danim' 통일·워치 컴플리케이션 추천 캡 제거(§11)·⑥ 위젯 종료 버튼(§10)·진행률 환산 캡+수행일 집계+수행일 아님 표기(§4.2)·파일명/CSV 헤더 현지화(§7)·워치 컴플리케이션 목표 필터(§6.4·§11, 사용자 승인 소기능)·기록 시각 입력 상시 휠 다이얼+분 절사(§6.5)·일기 달력 기분 사진 백레이어(§6.7)·다짐 없는 목표 '다짐 없음' 표기(§4.3)·워치 측정 중 행 탭 종료(§11)·도움말 예시 스크린샷 18장(§6.8)·설정 하루 시작 시간 접힘 행(§6.5)·**모음 탭 즐겨찾기 모아 보기**(§6.1 — 행동 많아질 때 혼잡도 대응, 사용자 승인 소기능. v1 별도 카드 가로 줄[대안 A~F 중 A]이 "따로 노는 느낌" 실사용 피드백으로 v2 그리드 섹션 통합+나머지 접기로 교체, 같은 날 후속 피드백으로 편집 모드 섹션 인지·섹션 간 드래그 지정/해제·"지정 순서대로 뒤에" 규칙 추가, 2026-07-22)·**en 복수형 정리**(2026-07-22 — 수량 1일 때 "1 times/quests" 류 오류를 카탈로그 plural variation으로 14건 전환[iOS 10·위젯 3·워치 1], 모음 탭 섹션 제목 en "Favorite"→"Favorites"로 워치와 통일. ko/ja 무영향). **App Store 등록 자료는 `Marketing/`에 완비**(설명·키워드·구독 메타·스크린샷 원본/합성판, README에 재생성 절차 — 앱 변경이 자료에 영향 주면 요청 없이 함께 갱신할 것. 2026-07-22 즐겨찾기 섹션(v2) 반영해 아이폰·아이패드 01-main과 help-main ko/en/ja 재촬영·합성판 재생성. **2026-07-29 스크린샷 세트 확장** — 아이폰 8장(+위젯·버블 메뉴·iCloud 동기화 합성·다크 모드)/아이패드 7장(+주간 타임테이블·위젯·동기화 합성) × ko/en/ja, 촬영·합성 절차는 Marketing/README '추가 스크린샷' 절. **1.1 제출 때 ASC 업로드 완료**). **사업자 등록·통신판매업 신고 완료(2026-07-22)** — 서류 절차 끝, ASC 등록·유료 계약 진행 가능(순서는 Marketing/README 체크리스트). 사용자는 계속 실사용 테스트 중이며 버그 보고 시: 최소 변경 + 시뮬레이터 재검증 + l10n 루틴(§2.3) + Debug/Store(워치 파일이면 워치까지) 빌드 + 커밋·푸시 | | 출시·운영 메모 (2026-07-28) | **ASC 등록·심사·출시 전부 완료** — 제출 1건에 앱 1.0+구독 그룹+상품 3종 동반(신형 ASC는 각 항목의 "심사에 추가"로 하나의 제출 초안에 담는 방식), '앱 개인정보 보호'="데이터 수집 안 함", 유료 앱 계약·대한민국 세금 양식 활성, 소규모 사업자 프로그램 가입. 개발자 본인 프리미엄은 **평생 이용권 특가 코드(무료 Offer Code) 교환**으로 스토어 버전에 적용됨(IAP용 프로모션 코드는 2026-03 폐지 — 이후 무료 지급은 특가 코드만 가능, 생성 후 교환 가능까지 최대 1시간). 상시 참고: `ITSAppUsesNonExemptEncryption=NO`, PrivacyInfo.xcprivacy 4개 번들(수집·트래킹 없음), 개인정보처리방침=`github.com/notalentprogrammer/HaruDanim` PRIVACY.md·이용약관=Apple 표준 EULA(결제 화면·앱 설명 모두 링크). **iCloud 프로덕션 동기화 확인 완료(2026-07-28)** — 아이폰·아이패드·맥 3기기 반나절 실사용에서 기존 데이터 포함 정상 왕복(개발→프로덕션 CloudKit 환경 전환에 따른 재업로드 우려 해소. 향후 동기화 문제 보고 시 이 전환 이력 참고). **정산 계좌 변경 완료** — 1.0 출시 시점에 이미 처리(2026-08-06 사용자 확인). 남은 운영 예정 작업 없음. 절차 상세 기록은 Marketing/README.md 체크리스트 | --- @@ -244,7 +244,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f - 테마(**시스템 설정 따름(기본)**/라이트/다크 — 즉시 적용, App Group 저장으로 위젯 공유. "system"은 스킴을 강제하지 않아 앱·위젯 모두 시스템 모드를 따름 — 2026-07-23 UX 감사에서 추가, 기본값도 light→system) / 언어(재시작 적용, AppleLanguages 오버라이드) - iPhone만: 내비게이션 방식(탭바/버블) + 탭바 구성 or 버블 순서·두 번 눌러 바로 이동(토글+대상 탭) - 모음 탭 목표 카드 선택(개수 무제한)+표시 방식 / 주 시작 요일 / 하루 시작 시간 / 짧은 기록 무시(**전 기기 공통 — iCloud 키-값 저장소 동기화**(1.1, `IOS/Core/SettingsCloudSync.swift` + `ubiquity-kvstore-identifier` 엔타이틀먼트): 검사는 종료를 실행한 기기의 설정 기준이라 기기별 값이면 교차 사용(아이패드 시작→아이폰 종료)에서 결과가 갈리던 실사용 보고의 해소. 값 저장은 여전히 App Group defaults고 KVS가 미러 — 시작 시 iCloud 값 채택(없으면 로컬 값 시드), 외부 변경·포그라운드 복귀 때 pull, 설정 변경 때 push(동일 값 가드로 되울림 방지). CloudKit 데이터 동기화(프리미엄 토글)와 별개라 iCloud 로그인만으로 동작, 미로그인이면 로컬 값 유지. 워치 종료는 페어링 아이폰에서 실행되므로 아이폰 설정 적용. 다른 Int 설정을 공통화하려면 syncedKeys에 추가만 하면 됨) / **장시간 측정 알림**(1~12시간, 켜는 순간 알림 권한 요청 — `SessionAlertManager`가 LiveActivityManager.sync 길목에서 진행 세션과 예약을 동기화, 세션 종료·설정 변경 시 예약 제거) / 대표 시간 기준(가장 먼저·나중에 시작 — 기본 latest) -- 데이터: **데이터 내보내기** — 행동·시간·횟수·목표·다짐을 CSV 5파일로 (ISO 8601, UTF-8 BOM, 무료 포함. **1.4 구간 지정**: 전체(기본)/직접 지정 — 기록만 구간으로 거르고[세션은 겹침 포함] 행동·목표·다짐 목록은 항상 전체[가져오기 연결·백업 온전성], 구간 파일명 접미 -yyyyMMdd-yyyyMMdd. 생성 코어는 `IOS/Core/CSVExport.swift`로 추출 — 초기화 백업과 공유. 검증 `-dataExportRangeDays`) / **데이터 정리·초기화**(1.4, 무료 — `IOS/Core/DataReset.swift`+`ArchiveReport.swift`+`DataResetView`): 범위 전체/처음~지정 날짜(포함)까지. 범위 삭제는 기록·일기만(완전 포함 세션만 삭제 — 경계 걸침·진행 중 보존, 일기 dayKey≤키, 구조·양식 보존), 전체는 모든 도메인 데이터+LocalPrefs 6키(설정·프리미엄 보존). ⚠️컨텍스트 경유 개별 삭제만(스토어 파일 통삭제 금지 — CloudKit 전파). 삭제 전 백업 3종(CSV·일기 PDF·**정리 리포트**[ArchiveReport — 타이틀+기간(첫~마지막 기록일)+히어로+행동별 누적 표+꼬리표 합계+월별 표+목표 이력+**목표별 다짐 표**(그룹=목표, 대상·주기·목표량+방향 — 검수 후속으로 추가, 2026-08-11)를 ExportPosterView 다페이지로, 24행 청크(다짐은 목표 그룹 단위 분할), PDF/스티치 PNG]) — 백업 켰으면 파일 생성 후에만 삭제 가능, 2단계 확인(다이얼로그→얼럿)+iCloud 전 기기·연속 끊김 경고. 검증 `-dataResetTest` 18건 / **데이터 가져오기**(1.1, 프리미엄 — `IOS/Core/CSVImport.swift`+`DataImportView`): 내보낸 CSV에서 행동·시간·횟수 기록 복원. **추가 전용**(수정·삭제 없음), 파일 선택(복수)→미리 보기→확인→저장 1회(DataChange.commit), **멱등**(기록 중복은 초 단위 절사 동일성으로 건너뜀 — 내보내기가 초 단위라 원본의 소수점 초 유사 중복까지 흡수), 파일 종류는 헤더가 아니라 **데이터 행 모양으로 판별**(UUID·ISO 날짜·time/count — 내보낸 언어와 무관), 행동 연결은 uuid 우선→이름+방식 폴백→새로 생성(csv uuid 보존, 꼬리표는 이름으로 재사용/프리셋 색 생성), '측정 중'(종료 없음) 행은 건너뜀(유령 Live Activity 방지), 목표·다짐은 비대상(다짐 CSV는 주기가 표시 문자열이라 재구성 불가 — 사용자 결정). 프리미엄 게이트 이유: CSV로 무료 한도(행동 10개) 우회 방지. 검증 `-csvImportTest`(§14) +- 데이터: **데이터 내보내기** — 행동·시간·횟수·목표·다짐을 CSV 5파일로 (ISO 8601, UTF-8 BOM, 무료 포함. **1.4 구간 지정**: 전체(기본)/직접 지정 — 기록만 구간으로 거르고[세션은 겹침 포함] 행동·목표·다짐 목록은 항상 전체[가져오기 연결·백업 온전성], 구간 파일명 접미 -yyyyMMdd-yyyyMMdd. 생성 코어는 `IOS/Core/CSVExport.swift`로 추출 — 초기화 백업과 공유. 검증 `-dataExportRangeDays`) / **데이터 정리·초기화**(1.4, 무료 — `IOS/Core/DataReset.swift`+`ArchiveReport.swift`+`DataResetView`): 범위 전체/처음~지정 날짜(포함)까지. 범위 삭제는 기록·일기만(완전 포함 세션만 삭제 — 경계 걸침·진행 중 보존, 일기 dayKey≤키, 구조·양식 보존), 전체는 모든 도메인 데이터+LocalPrefs 6키(설정·프리미엄 보존). ⚠️컨텍스트 경유 개별 삭제만(스토어 파일 통삭제 금지 — CloudKit 전파). 삭제 전 백업 3종(CSV·일기 PDF·**정리 리포트**[ArchiveReport — 타이틀+기간(첫~마지막 기록일)+히어로+행동별 누적 표+꼬리표 합계+월별 표+목표 이력+**목표별 다짐 표**(그룹=목표, 대상·주기·목표량+방향 — 검수 후속으로 추가, 2026-08-11)를 ExportPosterView 다페이지로, 24행 청크(다짐은 목표 그룹 단위 분할), PDF/스티치 PNG]) — 백업 켰으면 파일 생성 후에만 삭제 가능, 2단계 확인(다이얼로그→얼럿)+iCloud 전 기기·연속 끊김 경고. 검증 `-dataResetTest` 27건 / **데이터 가져오기**(1.1, 프리미엄 — `IOS/Core/CSVImport.swift`+`DataImportView`): 내보낸 CSV에서 행동·시간·횟수 기록 복원. **추가 전용**(수정·삭제 없음), 파일 선택(복수)→미리 보기→확인→저장 1회(DataChange.commit), **멱등**(기록 중복은 초 단위 절사 동일성으로 건너뜀 — 내보내기가 초 단위라 원본의 소수점 초 유사 중복까지 흡수), 파일 종류는 헤더가 아니라 **데이터 행 모양으로 판별**(UUID·ISO 날짜·time/count — 내보낸 언어와 무관), 행동 연결은 uuid 우선→이름+방식 폴백→새로 생성(csv uuid 보존, 꼬리표는 이름으로 재사용/프리셋 색 생성), '측정 중'(종료 없음) 행은 건너뜀(유령 Live Activity 방지), 목표·다짐은 비대상(다짐 CSV는 주기가 표시 문자열이라 재구성 불가 — 사용자 결정). 프리미엄 게이트 이유: CSV로 무료 한도(행동 10개) 우회 방지. 검증 `-csvImportTest`(§14) - 프리미엄 화면(§8) / 도움말(`HelpView` — 기기별 주제 분기, DisclosureGroup. **그룹당 대표 주제 1개에 예시 스크린샷**(IOS/HelpImages/help-<이름>-.jpg, 총 18장 ≈1.6MB) — 언어별 파일을 HelpImage 로더가 표시 언어로 선택(jpg라 UIImage(named:) 대신 URL 로드). 앱 화면·문구가 크게 바뀌면 해당 스크린샷도 재촬영할 것: 시뮬 로케일별 재시드 후 §14 인자로 촬영, 워치는 상태바 크롭) / 버전(번들 CFBundleShortVersionString 표시) --- @@ -352,7 +352,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f | 측정 알림 | `-settings.longSessionAlertHours `(설정 주입) `-longSessionAlertTestSeconds `(시간 대신 N초 발화, provisional 권한 자동) `-alertDump`(예약 상태 → Documents/session-alerts.txt) | | 목표 | `-goalShowEditor` `-goalShowFinished` `-goalReorder` `-goalScrollBottom` `-questShowEditor`(다짐 편집기 표시 — 마감 시각 다짐 우선, 없으면 첫 다짐) `-endGoalYesterday "제목"`(종료일을 어제로 — 실행 시점 자동 판정 검증) `-watchGoals `(생성순 목표 N개를 '애플워치에서 보기'로 설정, 0=전체 해제 — 설정 후 스냅숏 즉시 푸시, 컴플리케이션 필터 검증용) `-streakDump`(모든 다짐의 연속 계산 결과 → Documents/streak-dump.txt) `-progressSelfTest`(spanProgress 수치 49건 자가 검증[환산 캡·수행일 집계·마감·atMost·custom·주기 몫 완료+목표 하루 평균+과거 기준 소급 차단] → Documents/progress-self-test.txt — 인메모리 컨테이너·설정 고정이라 실데이터/기기 설정 무영향) `-seedPeriodFulfilled YES`(주기 몫을 이미 채운 주간·월간 다짐 목표 1개 시드 — '이번 주/달 달성' 표기 검증용, 기본 시드와 분리·이름 비현지화. 기록은 주/달 첫날 09:00이라 실행일이 첫날이면 오늘 기록이 되지만 완료 표기는 동일) | | 기록/통계 | `-historyMode timetable` `-historyWeekly` `-historyDaysAgo `(N일 전 날짜로 시작 — 지난주 등 과거 주 표시, 마케팅 촬영용) `-excludeActions "이름,이름"` `-historyShowFilter` `-statShowFilter` `-statSpan ` `-statRolling`('오늘 기준' 롤링 토글 켠 상태로 시작 — 1.4) `-statScrollBottom` `-showExport` `-exportDump`(Documents/export-dump.png) `-recordShowEditor `(최신 기록 수정 시트) | -| 설정/기타 | `-settingsScrollGoal` `-settingsScrollPremium` `-settingsScrollMeasure`(측정 섹션 — 짧은 기록 무시 푸터 검증) `-settingsShowDayStartWheel`(하루 시작 시간 휠 펼침 시작) `-premiumPreview` `-helpPreview`(+`-helpExpandAll`: 전 주제 펼침, `-helpScrollTo `: 이미지 주제로 스크롤) `-dataExportPreview`(+`-dataExportRun`이면 CSV 자동 생성 후 Documents 복사, `-dataExportRangeDays `이면 최근 N일 구간 지정 상태로 시작 — 1.4) `-dataImportPreview`(가져오기 화면 직접 표시 — -premium과 함께) `-csvImportTest`(CSV 가져오기 자가 검증 20건[파서·판별·연결·중복·멱등] → Documents/csv-import-test.txt — 인메모리 컨테이너라 실데이터 무영향) `-dataResetPreview`(데이터 정리·초기화 화면 직접 표시 — 1.4. +`-dataResetBackupRun pdf|image`면 백업 3종 자동 생성 후 Documents 복사, `-dataResetRangeDays `이면 날짜 지정[오늘-N일까지]으로 시작) `-dataResetTest`(초기화 자가 검증 18건[범위 삭제 경계·보존·cascade·전체 초기화] → Documents/data-reset-test.txt — 인메모리·설정 고정, 실데이터 무영향) `-symbolAuditDump`(아이콘 카탈로그 중 현재 OS에 없는 심볼 → Documents/symbol-audit.txt — 하위 OS QA, 1.4) `-actionShowFavoriteOrder`(행동 탭 즐겨찾기 순서 시트 — `-startTab action`과 함께) `-showOnboarding`(온보딩 강제 표시) `-widgetPreview YES|lock|marketing`(marketing = 디버그 라벨 없는 위젯 실렌더 모아 보기 — 마케팅 스크린샷 05-widgets/06-widgets 촬영용, Marketing/README 참고) `-widgetPreviewScroll <앵커>` `-widgetRenderMode accented|vibrant`(틴트/투명 홈 렌더링 분기 검증) `cloudSync`(standard bool로 강제) `-intentSmokeTest`(시리 인텐트 10개 시나리오 실행 → Documents/intent-smoke-test.txt, -seedDemo·-premium과 함께) | +| 설정/기타 | `-settingsScrollGoal` `-settingsScrollPremium` `-settingsScrollMeasure`(측정 섹션 — 짧은 기록 무시 푸터 검증) `-settingsShowDayStartWheel`(하루 시작 시간 휠 펼침 시작) `-premiumPreview` `-helpPreview`(+`-helpExpandAll`: 전 주제 펼침, `-helpScrollTo `: 이미지 주제로 스크롤) `-dataExportPreview`(+`-dataExportRun`이면 CSV 자동 생성 후 Documents 복사, `-dataExportRangeDays `이면 최근 N일 구간 지정 상태로 시작 — 1.4) `-dataImportPreview`(가져오기 화면 직접 표시 — -premium과 함께) `-csvImportTest`(CSV 가져오기 자가 검증 20건[파서·판별·연결·중복·멱등] → Documents/csv-import-test.txt — 인메모리 컨테이너라 실데이터 무영향) `-dataResetPreview`(데이터 정리·초기화 화면 직접 표시 — 1.4. +`-dataResetBackupRun pdf|image`면 백업 3종 자동 생성 후 Documents 복사, `-dataResetRangeDays `이면 날짜 지정[오늘-N일까지], `-dataResetScope diary`면 '일기만' 선택으로 시작) `-dataResetTest`(초기화 자가 검증 27건[범위 삭제 경계·보존·cascade·전체 초기화·일기만] → Documents/data-reset-test.txt — 인메모리·설정 고정, 실데이터 무영향) `-symbolAuditDump`(아이콘 카탈로그 중 현재 OS에 없는 심볼 → Documents/symbol-audit.txt — 하위 OS QA, 1.4) `-actionShowFavoriteOrder`(행동 탭 즐겨찾기 순서 시트 — `-startTab action`과 함께) `-showOnboarding`(온보딩 강제 표시) `-widgetPreview YES|lock|marketing`(marketing = 디버그 라벨 없는 위젯 실렌더 모아 보기 — 마케팅 스크린샷 05-widgets/06-widgets 촬영용, Marketing/README 참고) `-widgetPreviewScroll <앵커>` `-widgetRenderMode accented|vibrant`(틴트/투명 홈 렌더링 분기 검증) `cloudSync`(standard bool로 강제) `-intentSmokeTest`(시리 인텐트 10개 시나리오 실행 → Documents/intent-smoke-test.txt, -seedDemo·-premium과 함께) | | 버블 | `-radialExpanded` `-radialDoubleTapTest`(2초 뒤 FAB 더블탭 재현 — `-settings.radialDoubleTapTab <탭>`과 함께) | | 일기 | `-diarySeed`(필기 획 2줄 포함 — 달력 화면에서 먼저 시드 후 재실행으로 열 것) `-diaryDrawMode`(맥 분기에서 '그리기' 토글 켠 채 시작) `-diaryOpenToday`(**`-startTab diary` 필수**) `-diaryPage ` `-diaryZoomScale <배>`(2.5초 뒤 fit×N 프로그램 줌 — 획 유지·선명도 확인) `-diaryShowConfig` `-diaryShowExport`(달력 화면 onAppear — `-diaryOpenToday`와 함께 쓰면 안 뜸) `-diaryExportRun pdf|image`(결과를 Documents로 복사) `-diaryShowCalendarPicker` `-diarySeedEvents` `-diaryHideFirstAction` `-diaryShowActionFilter` `-diaryHideFirstGoal` `-diaryShowGoalFilter` `-diaryShowDefaults`(기본 표시 설정 시트) `-diaryDefaultsSeed`(첫 목표·첫 행동 기본 숨김+캘린더 기본 '모든 캘린더' 주입 — 기본값 적용 검증용) `-diarySeedPhoto`(오늘=이모지+사진·어제=사진만 기분 사진 시드 — 달력 백레이어 검증) `-diarySeedTemplate`(2쪽 샘플 PDF 양식 생성) `-diaryLockScreen`(잠금 게이트 화면 강제 — 인증 없이 표시) `-diaryShowTemplates`(양식 관리 시트) `-diaryShowPageChooser`(페이지 추가 선택 시트) `-diaryApplyTemplate`(첫 양식으로 페이지 추가) `-diaryShapeEdit`(활성 노트 페이지 첫 도형 선택+'도형 수정' 시트 — `-diarySeed`·`-diaryOpenToday`·`-diaryPage 1`과 함께. 시드 요소들이 스타일 필드[굵기·둥글기·채움·자유 비율·회전·글자 크기·정렬·테두리 색]를 전부 렌더) `-diaryTextEdit`(첫 텍스트 상자를 제자리 입력 상태로 — 같은 조합) `-diaryStrokeTest`(3초 뒤 프로그램 획 추가로 디바운스 저장 파이프라인 검증 → Documents/diary-stroke-test.txt[strokes·delivered] — `-diaryOpenToday`와 함께, 1.4) `-diaryExportDeleteTest`(내보내기 완료 후 '삭제' 버튼과 같은 가드·경로로 삭제 실행 — `-diaryShowExport -diaryExportRun`과 함께, 재실행 후 달력 마커 소멸로 판정, 1.4) / 섹션 강제: `-diary.sectionOrder "timetable,hero,goals"` `-diary.hiddenSections "mood,todos,records,bars"` | | 워치 | `-complicationPreview` `-complicationScroll` `-complicationScrollTo ` `-autoRunFirstAction` `-autoStopRunning`(측정 중 행 탭과 동일 경로로 대표 측정 종료 — 페어링 심 E2E용) | diff --git a/myApp/HaruDanim/Haru_Danim.xcodeproj/project.pbxproj b/myApp/HaruDanim/Haru_Danim.xcodeproj/project.pbxproj index f1813e3..8dd1df2 100644 --- a/myApp/HaruDanim/Haru_Danim.xcodeproj/project.pbxproj +++ b/myApp/HaruDanim/Haru_Danim.xcodeproj/project.pbxproj @@ -601,7 +601,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = IOS/Haru_Danim.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 5GNS4Z2SYT; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -643,7 +643,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = IOS/Haru_Danim.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 5GNS4Z2SYT; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -686,7 +686,7 @@ CODE_SIGN_ENTITLEMENTS = "Haru_DanimWatch Watch App/Haru_DanimWatch Watch App.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 5GNS4Z2SYT; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -722,7 +722,7 @@ CODE_SIGN_ENTITLEMENTS = "Haru_DanimWatch Watch App/Haru_DanimWatch Watch App.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 5GNS4Z2SYT; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -758,7 +758,7 @@ CODE_SIGN_ENTITLEMENTS = Haru_DanimWatchWidgets/Haru_DanimWatchWidgetsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 5GNS4Z2SYT; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist; @@ -794,7 +794,7 @@ CODE_SIGN_ENTITLEMENTS = Haru_DanimWatchWidgets/Haru_DanimWatchWidgetsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 5GNS4Z2SYT; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist; @@ -827,7 +827,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = Widgets/Haru_DanimWidgets.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 5GNS4Z2SYT; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Widgets/Info.plist; @@ -853,7 +853,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = Widgets/Haru_DanimWidgets.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 5GNS4Z2SYT; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Widgets/Info.plist; diff --git a/myApp/HaruDanim/IOS/Core/DataReset.swift b/myApp/HaruDanim/IOS/Core/DataReset.swift index 511afe0..b8e363b 100644 --- a/myApp/HaruDanim/IOS/Core/DataReset.swift +++ b/myApp/HaruDanim/IOS/Core/DataReset.swift @@ -62,6 +62,25 @@ enum DataReset { return counts } + // MARK: 일기만 삭제 (1.4(2) — 기록·구조·양식은 보존) + + /// 일기만 삭제한다. endDayKey가 nil이면 모든 일기, 지정하면 그 달력일(포함)까지. + /// cascade로 페이지·요소·할 일·기분 사진까지 정리되고, 양식 라이브러리는 남긴다. + @MainActor + static func deleteDiaries(upToDayKey endDayKey: Date?, context: ModelContext) throws -> Counts { + var counts = Counts() + let descriptor: FetchDescriptor + if let endDayKey { + descriptor = FetchDescriptor(predicate: #Predicate { $0.dayKey <= endDayKey }) + } else { + descriptor = FetchDescriptor() + } + let entries = try context.fetch(descriptor) + for entry in entries { context.delete(entry) } + counts.diaryEntries = entries.count + return counts + } + // MARK: 전체 초기화 /// 모든 도메인 데이터 삭제 (cascade: 목표→다짐, 행동→기록, 일기→페이지·요소·할 일). @@ -210,6 +229,43 @@ enum DataReset { expect("전체: 목표 0", (try? context.fetchCount(FetchDescriptor())) ?? -1, 0) expect("전체: 일기 0", (try? context.fetchCount(FetchDescriptor())) ?? -1, 0) expect("전체: 양식 0", (try? context.fetchCount(FetchDescriptor())) ?? -1, 0) + + // ---- 2부: 일기만 삭제 (새 컨테이너 — 기록·구조 보존 확인) ---- + let container2 = try ModelContainer( + for: DataStore.schema, + configurations: [ModelConfiguration(isStoredInMemoryOnly: true)] + ) + let context2 = container2.mainContext + let action2 = Action(name: "일기검증행동", symbolName: "book.fill", trackingType: .time, sortOrder: 0) + context2.insert(action2) + context2.insert(TimeSession(action: action2, + startAt: cutUpper.addingTimeInterval(-7200), + endAt: cutUpper.addingTimeInterval(-3600))) + let template2 = DiaryTemplate(name: "양식2", kind: .image) + context2.insert(template2) + for offset in [-1, 0, 1] { + let entry = DiaryEntry(dayKey: cal.date(byAdding: .day, value: offset, to: cutKey)!) + context2.insert(entry) + let page = DiaryPage(index: 1) + page.entry = entry + context2.insert(page) + } + try context2.save() + + let diaryRange = try DataReset.deleteDiaries(upToDayKey: cutKey, context: context2) + try context2.save() + expect("일기만(날짜까지): 삭제 수(기준일 포함)", diaryRange.diaryEntries, 2) + expect("일기만(날짜까지): 남은 일기", (try? context2.fetchCount(FetchDescriptor())) ?? -1, 1) + expect("일기만(날짜까지): 페이지 cascade", (try? context2.fetchCount(FetchDescriptor())) ?? -1, 1) + expect("일기만(날짜까지): 기록 보존", (try? context2.fetchCount(FetchDescriptor())) ?? -1, 1) + + let diaryAll = try DataReset.deleteDiaries(upToDayKey: nil, context: context2) + try context2.save() + expect("일기만(전체): 삭제 수", diaryAll.diaryEntries, 1) + expect("일기만(전체): 일기 0", (try? context2.fetchCount(FetchDescriptor())) ?? -1, 0) + expect("일기만(전체): 기록 보존", (try? context2.fetchCount(FetchDescriptor())) ?? -1, 1) + expect("일기만(전체): 행동 보존", (try? context2.fetchCount(FetchDescriptor())) ?? -1, 1) + expect("일기만(전체): 양식 보존", (try? context2.fetchCount(FetchDescriptor())) ?? -1, 1) } catch { failures += 1 lines.append("FAIL 예외: \(error)") diff --git a/myApp/HaruDanim/IOS/Localizable.xcstrings b/myApp/HaruDanim/IOS/Localizable.xcstrings index 0313044..2f797dd 100644 --- a/myApp/HaruDanim/IOS/Localizable.xcstrings +++ b/myApp/HaruDanim/IOS/Localizable.xcstrings @@ -611,6 +611,22 @@ } } }, + "%@까지의 일기를 삭제할까요?": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete diaries up to %@?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@までの日記を削除しますか?" + } + } + } + }, "%@요일": { "localizations": { "en": { @@ -2861,6 +2877,22 @@ } } }, + "기록과 일기": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Records & Diaries" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "記録と日記" + } + } + } + }, "기록에 메모 남기기": { "localizations": { "en": { @@ -2893,18 +2925,18 @@ } } }, - "기록이 오래 쌓여 정리하고 싶다면 설정 → 데이터 → '데이터 정리·초기화'를 써 보세요. 전체를 비우거나, 처음부터 지정한 날짜까지의 기록·일기만 삭제할 수 있어요 — 이때 행동·꼬리표·목표·다짐과 일기 양식은 남고, 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정도 남아요. 삭제 전에 CSV 백업과, 삭제할 기록을 한눈에 정리한 '정리 리포트'(PDF·이미지)를 만들어 보관할 수 있어요 — 백업을 켰다면 파일을 만든 뒤에만 삭제로 진행할 수 있어요. 과거 기록이 사라지면 다짐의 연속 달성 기록은 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제되니 백업을 꼭 저장해 두세요.": { + "기록이 오래 쌓여 정리하고 싶다면 설정 → 데이터 → '데이터 정리·초기화'를 써 보세요. 전체를 비우거나, 처음부터 지정한 날짜까지의 기록·일기만 삭제할 수 있어요 — 이때 행동·꼬리표·목표·다짐과 일기 양식은 남고, 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정도 남아요. '일기만'을 고르면 기록은 그대로 두고 일기만 지울 수도 있어요 — 사진·필기로 커진 일기 용량만 줄일 때 좋아요. 삭제 전에 CSV 백업과, 삭제할 기록을 한눈에 정리한 '정리 리포트'(PDF·이미지)를 만들어 보관할 수 있어요 — 백업을 켰다면 파일을 만든 뒤에만 삭제로 진행할 수 있어요. 과거 기록이 사라지면 다짐의 연속 달성 기록은 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제되니 백업을 꼭 저장해 두세요.": { "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "If records have piled up, try Settings → Data → Data Cleanup & Reset. You can clear everything, or delete only records and diaries from the beginning through a chosen date — actions, tags, goals, quests, and diary templates remain, and sessions spanning the chosen date or still running are kept too. Before deleting, you can create a CSV backup plus a summary report (PDF/image) that sums up what will be removed at a glance — if backups are on, deletion proceeds only after the files are created. Removing past records breaks quest streaks. With iCloud sync on, deletion applies to your other devices too, so be sure to save the backups." + "value": "If records have piled up, try Settings → Data → Data Cleanup & Reset. You can clear everything, or delete only records and diaries from the beginning through a chosen date — actions, tags, goals, quests, and diary templates remain, and sessions spanning the chosen date or still running are kept too. Choose 'Diaries only' to delete just the diaries while keeping records — handy for freeing up space taken by photos and handwriting. Before deleting, you can create a CSV backup plus a summary report (PDF/image) that sums up what will be removed at a glance — if backups are on, deletion proceeds only after the files are created. Removing past records breaks quest streaks. With iCloud sync on, deletion applies to your other devices too, so be sure to save the backups." } }, "ja": { "stringUnit": { "state": "translated", - "value": "記録がたまって整理したいときは、設定 → データ → 「データ整理・リセット」を試してください。全体を空にするか、最初から指定した日付までの記録・日記だけを削除できます — アクション・タグ・目標・クエストと日記テンプレートは残り、指定日をまたぐ計測や進行中の計測も残ります。削除前にCSVバックアップと、削除する記録をひと目でまとめた「整理レポート」(PDF・画像)を作成して保管できます — バックアップをオンにした場合は、ファイルを作成した後にのみ削除に進めます。過去の記録が消えるとクエストの連続達成は途切れます。iCloud同期がオンの場合は他のデバイスでも削除されるため、必ずバックアップを保存してください。" + "value": "記録がたまって整理したいときは、設定 → データ → 「データ整理・リセット」を試してください。全体を空にするか、最初から指定した日付までの記録・日記だけを削除できます — アクション・タグ・目標・クエストと日記テンプレートは残り、指定日をまたぐ計測や進行中の計測も残ります。「日記のみ」を選ぶと、記録はそのままに日記だけを削除することもできます — 写真や手書きで大きくなった日記の容量だけを減らしたいときに便利です。削除前にCSVバックアップと、削除する記録をひと目でまとめた「整理レポート」(PDF・画像)を作成して保管できます — バックアップをオンにした場合は、ファイルを作成した後にのみ削除に進めます。過去の記録が消えるとクエストの連続達成は途切れます。iCloud同期がオンの場合は他のデバイスでも削除されるため、必ずバックアップを保存してください。" } } } @@ -5525,6 +5557,54 @@ } } }, + "모든 일기 삭제…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete All Diaries…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべての日記を削除…" + } + } + } + }, + "모든 일기(필기·사진·할 일 포함)를 삭제해요. 시간·횟수 기록과 행동·목표, 일기 양식은 그대로 남아요 — 사진·필기로 커진 일기 용량만 줄일 때 좋아요.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deletes every diary (handwriting, photos, and to-dos included). Time/count records, actions, goals, and diary templates remain — handy for freeing up space taken by photos and handwriting." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべての日記(手書き・写真・やることを含む)を削除します。時間・回数の記録とアクション・目標、日記テンプレートはそのまま残ります — 写真や手書きで大きくなった日記の容量だけを減らしたいときに便利です。" + } + } + } + }, + "모든 일기를 삭제할까요?": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete all diaries?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべての日記を削除しますか?" + } + } + } + }, "모든 캘린더": { "localizations": { "en": { @@ -7181,6 +7261,22 @@ } } }, + "삭제할 일기들을 PDF 하나로 만들어 보관할 수 있어요. 백업을 켰다면 파일을 만들어 저장한 뒤에 삭제할 수 있어요.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "You can turn the diaries to be deleted into a single PDF for safekeeping. If the backup is on, you can delete only after creating and saving the file." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "削除する日記を1つのPDFにまとめて保管できます。バックアップをオンにした場合は、ファイルを作成・保存してから削除できます。" + } + } + } + }, "상태": { "localizations": { "en": { @@ -10802,6 +10898,38 @@ } } }, + "일기만": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Diaries only" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "日記のみ" + } + } + } + }, + "일기의 필기·사진·할 일이 함께 삭제돼요. 시간·횟수 기록과 행동·목표, 일기 양식은 남아요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The diaries' handwriting, photos, and to-dos are deleted together. Time/count records, actions, goals, and diary templates remain. With iCloud sync on, they are deleted from other devices too." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "日記の手書き・写真・やることが一緒に削除されます。時間・回数の記録とアクション・目標、日記テンプレートは残ります。iCloud同期がオンの場合、他のデバイスからも削除されます。" + } + } + } + }, "일정": { "localizations": { "en": { @@ -11309,6 +11437,22 @@ } } }, + "정리할 데이터": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Data to Clean Up" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "整理するデータ" + } + } + } + }, "정리할 범위": { "localizations": { "en": { @@ -12237,6 +12381,22 @@ } } }, + "지정한 날짜까지 일기 삭제…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete Diaries Up to Date…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "指定した日付まで日記を削除…" + } + } + } + }, "지정한 날짜까지의 시간·횟수 기록과 일기가 삭제돼요. 과거 기록이 사라지면 다짐의 연속 달성 기록이 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요.": { "localizations": { "en": { @@ -12429,6 +12589,22 @@ } } }, + "처음부터 지정한 날짜(포함)까지의 일기만 삭제해요. 시간·횟수 기록과 행동·목표, 일기 양식은 그대로 남아요.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deletes only diaries from the beginning through the chosen date (inclusive). Time/count records, actions, goals, and diary templates remain." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最初から指定した日付(含む)までの日記だけを削除します。時間・回数の記録とアクション・目標、日記テンプレートはそのまま残ります。" + } + } + } + }, "첫 페이지 타임테이블 카드 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 표시할 수 있어요(일정은 점선 테두리로 구분돼요). 필터 버튼을 누르면 그날 기록된 행동 중 타임테이블에 보여줄 것만 고를 수 있고, '이 날의 목표' 카드에도 필터 버튼이 있어 보고 싶은 목표만 골라 둘 수 있어요. 선택은 모두 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 반영돼요.\n매일 같은 선택을 반복하지 않으려면 달력 화면 오른쪽 위 '기본 표시 설정'을 써 보세요 — 캘린더 일정을 기본으로 넣을지(모든 캘린더/선택한 캘린더만), 어떤 목표·행동을 기본으로 숨길지를 한 번만 정하면 모든 날짜에 적용돼요. 날짜별 필터로 다르게 고른 날짜는 그 선택이 우선하고, 필터의 '기본값으로 되돌리기'로 언제든 기본값으로 돌아갈 수 있어요.": { "localizations": { "en": { diff --git a/myApp/HaruDanim/IOS/Views/DataResetView.swift b/myApp/HaruDanim/IOS/Views/DataResetView.swift index cb7ecdb..d192e05 100644 --- a/myApp/HaruDanim/IOS/Views/DataResetView.swift +++ b/myApp/HaruDanim/IOS/Views/DataResetView.swift @@ -28,6 +28,19 @@ struct DataResetView: View { } } + /// 정리 대상 (1.4(2)): 기록·일기 전부 vs 일기만. + /// 일기만 모드는 사진·필기로 커진 일기 용량만 줄이고 기록·구조는 남긴다 + private enum ResetScope: String, CaseIterable, Identifiable { + case recordsAndDiary, diaryOnly + var id: String { rawValue } + var label: String { + switch self { + case .recordsAndDiary: return String(localized: "기록과 일기") + case .diaryOnly: return String(localized: "일기만") + } + } + } + private enum ReportFormat: String, CaseIterable, Identifiable { case pdf, image var id: String { rawValue } @@ -39,6 +52,7 @@ struct DataResetView: View { } } + @State private var scope: ResetScope = .recordsAndDiary @State private var rangeMode: RangeMode = .all @State private var endDate: Date = Calendar.current.date(byAdding: .month, value: -3, to: .now) ?? .now @State private var includeCSV = true @@ -63,10 +77,15 @@ struct DataResetView: View { rangeMode == .all ? nil : Date.distantPast.. → '날짜 지정'(오늘-N일까지)로 시작 let days = UserDefaults.standard.integer(forKey: "dataResetRangeDays") if days > 0 { @@ -182,6 +202,12 @@ struct DataResetView: View { private var rangeSection: some View { Section { + Picker("정리할 데이터", selection: $scope.animation()) { + ForEach(ResetScope.allCases) { scope in + Text(scope.label).tag(scope) + } + } + .pickerStyle(.segmented) Picker("범위", selection: $rangeMode) { ForEach(RangeMode.allCases) { mode in Text(mode.label).tag(mode) @@ -194,18 +220,27 @@ struct DataResetView: View { } header: { Text("정리할 범위") } footer: { - Text(rangeMode == .all - ? "모든 행동·꼬리표·목표·다짐·기록·일기·양식을 삭제하고 처음 상태로 돌아가요. 앱 설정(테마·하루 시작 시간 등)과 프리미엄은 그대로예요." - : "처음부터 지정한 날짜(포함)까지의 시간·횟수 기록과 일기만 삭제해요. 행동·꼬리표·목표·다짐과 양식 라이브러리는 남아요. 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정은 남아요.") + switch (scope, rangeMode) { + case (.recordsAndDiary, .all): + Text("모든 행동·꼬리표·목표·다짐·기록·일기·양식을 삭제하고 처음 상태로 돌아가요. 앱 설정(테마·하루 시작 시간 등)과 프리미엄은 그대로예요.") + case (.recordsAndDiary, .upTo): + Text("처음부터 지정한 날짜(포함)까지의 시간·횟수 기록과 일기만 삭제해요. 행동·꼬리표·목표·다짐과 양식 라이브러리는 남아요. 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정은 남아요.") + case (.diaryOnly, .all): + Text("모든 일기(필기·사진·할 일 포함)를 삭제해요. 시간·횟수 기록과 행동·목표, 일기 양식은 그대로 남아요 — 사진·필기로 커진 일기 용량만 줄일 때 좋아요.") + case (.diaryOnly, .upTo): + Text("처음부터 지정한 날짜(포함)까지의 일기만 삭제해요. 시간·횟수 기록과 행동·목표, 일기 양식은 그대로 남아요.") + } } } private var targetSection: some View { Section("삭제 대상") { - countRow("timer", String(localized: "시간 기록"), String(localized: "\(targetSessionCount)건")) - countRow("number", String(localized: "횟수 기록"), String(localized: "\(targetEntryCount)건")) + if scope == .recordsAndDiary { + countRow("timer", String(localized: "시간 기록"), String(localized: "\(targetSessionCount)건")) + countRow("number", String(localized: "횟수 기록"), String(localized: "\(targetEntryCount)건")) + } countRow("book.closed.fill", String(localized: "일기"), String(localized: "\(targetDiaryCount)일")) - if rangeMode == .all { + if scope == .recordsAndDiary && rangeMode == .all { countRow("figure.walk", String(localized: "행동"), String(localized: "\(targetActionCount)개")) countRow("flag.checkered", String(localized: "목표"), String(localized: "\(targetGoalCount)개")) } @@ -214,22 +249,27 @@ struct DataResetView: View { private var backupSection: some View { Section { - Toggle(isOn: $includeCSV) { - Label("CSV 내보내기", systemImage: "tablecells") + // 일기만 모드에서는 기록 백업(CSV·정리 리포트) 토글을 숨긴다 — 대상이 아님 + if scope == .recordsAndDiary { + Toggle(isOn: $includeCSV) { + Label("CSV 내보내기", systemImage: "tablecells") + } } Toggle(isOn: $includeDiary) { Label("일기 PDF", systemImage: "book.closed") } - Toggle(isOn: $includeReport) { - Label("정리 리포트", systemImage: "doc.richtext") - } - if includeReport { - Picker("리포트 형식", selection: $reportFormat) { - ForEach(ReportFormat.allCases) { format in - Text(format.label).tag(format) - } + if scope == .recordsAndDiary { + Toggle(isOn: $includeReport) { + Label("정리 리포트", systemImage: "doc.richtext") + } + if includeReport { + Picker("리포트 형식", selection: $reportFormat) { + ForEach(ReportFormat.allCases) { format in + Text(format.label).tag(format) + } + } + .pickerStyle(.segmented) } - .pickerStyle(.segmented) } if isGenerating { HStack { @@ -238,7 +278,7 @@ struct DataResetView: View { .foregroundStyle(.secondary) } } else if backupURLs.isEmpty { - if includeCSV || includeDiary || includeReport { + if wantsBackup { Button { makeBackups() } label: { @@ -259,7 +299,9 @@ struct DataResetView: View { } header: { Text("삭제 전 백업") } footer: { - Text("CSV는 '데이터 가져오기'로 복원할 수 있는 원본 백업이에요. 정리 리포트는 삭제할 기록을 한눈에 정리한 문서예요(기간·행동별 누적·꼬리표·월별·목표 이력). 백업을 켰다면 파일을 만들어 저장한 뒤에 삭제할 수 있어요.") + Text(scope == .diaryOnly + ? "삭제할 일기들을 PDF 하나로 만들어 보관할 수 있어요. 백업을 켰다면 파일을 만들어 저장한 뒤에 삭제할 수 있어요." + : "CSV는 '데이터 가져오기'로 복원할 수 있는 원본 백업이에요. 정리 리포트는 삭제할 기록을 한눈에 정리한 문서예요(기간·행동별 누적·꼬리표·월별·목표 이력). 백업을 켰다면 파일을 만들어 저장한 뒤에 삭제할 수 있어요.") } } @@ -275,8 +317,7 @@ struct DataResetView: View { Button(role: .destructive) { confirmingDelete = true } label: { - Label(rangeMode == .all ? "전체 데이터 삭제…" : "지정한 날짜까지 삭제…", - systemImage: "trash") + Label(deleteButtonTitle, systemImage: "trash") .frame(maxWidth: .infinity) } .disabled(!canDelete) @@ -297,8 +338,10 @@ struct DataResetView: View { Image(systemName: "checkmark.circle.fill") .foregroundStyle(AppTheme.green) } - countRow("timer", String(localized: "시간 기록"), String(localized: "\(counts.sessions)건")) - countRow("number", String(localized: "횟수 기록"), String(localized: "\(counts.entries)건")) + if scope == .recordsAndDiary { + countRow("timer", String(localized: "시간 기록"), String(localized: "\(counts.sessions)건")) + countRow("number", String(localized: "횟수 기록"), String(localized: "\(counts.entries)건")) + } countRow("book.closed.fill", String(localized: "일기"), String(localized: "\(counts.diaryEntries)일")) if counts.actions > 0 || counts.goals > 0 { countRow("figure.walk", String(localized: "행동"), String(localized: "\(counts.actions)개")) @@ -311,6 +354,39 @@ struct DataResetView: View { } } + private var confirmTitle: String { + switch (scope, rangeMode) { + case (.recordsAndDiary, .all): + return String(localized: "모든 데이터를 삭제할까요?") + case (.recordsAndDiary, .upTo): + return String(localized: "\(Format.fullDate(endKey))까지의 기록·일기를 삭제할까요?") + case (.diaryOnly, .all): + return String(localized: "모든 일기를 삭제할까요?") + case (.diaryOnly, .upTo): + return String(localized: "\(Format.fullDate(endKey))까지의 일기를 삭제할까요?") + } + } + + private var confirmMessage: String { + switch (scope, rangeMode) { + case (.recordsAndDiary, .all): + return String(localized: "행동·꼬리표·목표·다짐·기록·일기·양식이 모두 삭제돼요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요.") + case (.recordsAndDiary, .upTo): + return String(localized: "지정한 날짜까지의 시간·횟수 기록과 일기가 삭제돼요. 과거 기록이 사라지면 다짐의 연속 달성 기록이 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요.") + case (.diaryOnly, _): + return String(localized: "일기의 필기·사진·할 일이 함께 삭제돼요. 시간·횟수 기록과 행동·목표, 일기 양식은 남아요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제돼요.") + } + } + + private var deleteButtonTitle: String { + switch (scope, rangeMode) { + case (.recordsAndDiary, .all): return String(localized: "전체 데이터 삭제…") + case (.recordsAndDiary, .upTo): return String(localized: "지정한 날짜까지 삭제…") + case (.diaryOnly, .all): return String(localized: "모든 일기 삭제…") + case (.diaryOnly, .upTo): return String(localized: "지정한 날짜까지 일기 삭제…") + } + } + private func countRow(_ symbol: String, _ title: String, _ value: String) -> some View { HStack { Label { @@ -356,7 +432,7 @@ struct DataResetView: View { }() let diaryEntries = try context.fetch(diaryDescriptor).filter(\.hasContent) - if includeCSV { + if scope == .recordsAndDiary && includeCSV { // ⚠️ CSVExport.fileSuffix(for:)는 dayKeys 순회라 distantPast 범위 금지 — // '처음~까지'는 끝 날짜만 접미로 표기한다 var suffix = "" @@ -383,7 +459,7 @@ struct DataResetView: View { urls.append(url) } } - if includeReport { + if scope == .recordsAndDiary && includeReport { if let url = ArchiveReport.makeURL( format: reportFormat == .pdf ? .pdf : .image, actions: actions, sessions: sessions, entries: entries, goals: goals, @@ -419,9 +495,14 @@ struct DataResetView: View { try? await Task.sleep(for: .milliseconds(60)) do { let counts: DataReset.Counts - if rangeMode == .all { + switch (scope, rangeMode) { + case (.diaryOnly, .all): + counts = try DataReset.deleteDiaries(upToDayKey: nil, context: context) + case (.diaryOnly, .upTo): + counts = try DataReset.deleteDiaries(upToDayKey: endKey, context: context) + case (.recordsAndDiary, .all): counts = try DataReset.deleteAll(context: context) - } else { + case (.recordsAndDiary, .upTo): counts = try DataReset.deleteRecords(upToDayKey: endKey, context: context, math: math) } // 저장 + 위젯·워치·LiveActivity 동기화 (진행 중 측정도 전체 삭제면 함께 정리됨) diff --git a/myApp/HaruDanim/IOS/Views/HelpView.swift b/myApp/HaruDanim/IOS/Views/HelpView.swift index 1bfc439..9e80f3d 100644 --- a/myApp/HaruDanim/IOS/Views/HelpView.swift +++ b/myApp/HaruDanim/IOS/Views/HelpView.swift @@ -285,7 +285,7 @@ struct HelpView: View { HelpTopic( symbol: "trash", title: "데이터 정리·초기화", - body: "기록이 오래 쌓여 정리하고 싶다면 설정 → 데이터 → '데이터 정리·초기화'를 써 보세요. 전체를 비우거나, 처음부터 지정한 날짜까지의 기록·일기만 삭제할 수 있어요 — 이때 행동·꼬리표·목표·다짐과 일기 양식은 남고, 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정도 남아요. 삭제 전에 CSV 백업과, 삭제할 기록을 한눈에 정리한 '정리 리포트'(PDF·이미지)를 만들어 보관할 수 있어요 — 백업을 켰다면 파일을 만든 뒤에만 삭제로 진행할 수 있어요. 과거 기록이 사라지면 다짐의 연속 달성 기록은 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제되니 백업을 꼭 저장해 두세요." + body: "기록이 오래 쌓여 정리하고 싶다면 설정 → 데이터 → '데이터 정리·초기화'를 써 보세요. 전체를 비우거나, 처음부터 지정한 날짜까지의 기록·일기만 삭제할 수 있어요 — 이때 행동·꼬리표·목표·다짐과 일기 양식은 남고, 지정한 날짜에 걸쳐 있는 측정과 진행 중인 측정도 남아요. '일기만'을 고르면 기록은 그대로 두고 일기만 지울 수도 있어요 — 사진·필기로 커진 일기 용량만 줄일 때 좋아요. 삭제 전에 CSV 백업과, 삭제할 기록을 한눈에 정리한 '정리 리포트'(PDF·이미지)를 만들어 보관할 수 있어요 — 백업을 켰다면 파일을 만든 뒤에만 삭제로 진행할 수 있어요. 과거 기록이 사라지면 다짐의 연속 달성 기록은 끊겨요. iCloud 동기화를 켰다면 다른 기기에서도 함께 삭제되니 백업을 꼭 저장해 두세요." ), ]) } diff --git a/myApp/HaruDanim/IOS/Views/RadialNavigationView.swift b/myApp/HaruDanim/IOS/Views/RadialNavigationView.swift index 7039631..6ae8c8c 100644 --- a/myApp/HaruDanim/IOS/Views/RadialNavigationView.swift +++ b/myApp/HaruDanim/IOS/Views/RadialNavigationView.swift @@ -56,7 +56,15 @@ struct RadialTabView: View { ForEach(AppTab.phoneCases) { tab in Tab(tab.label, systemImage: tab.symbol, value: tab.rawValue) { NavigationStack { + // FAB(하단 42pt + 구슬 64pt)가 항상 떠 있으므로 스크롤 콘텐츠의 + // 끝이 그 위로 올라오게 하단 인셋을 준다 — 없으면 목록 마지막 행이 + // FAB에 가려지고 중앙 탭이 메뉴 열기로 새는 실측 문제(1.4(2) 검수). + // ⚠️ NavigationStack '안쪽'에 붙일 것 — Tab 콘텐츠(스택 바깥)에 + // 붙이면 TabView가 safe area를 재설정해 무시된다(실측) tab.rootView + .safeAreaInset(edge: .bottom) { + Color.clear.frame(height: 78) + } } .toolbar(.hidden, for: .tabBar) } diff --git a/myApp/HaruDanim/Marketing/AppStore/promotional-text-1.4.txt b/myApp/HaruDanim/Marketing/AppStore/promotional-text-1.4.txt new file mode 100644 index 0000000..aafce63 --- /dev/null +++ b/myApp/HaruDanim/Marketing/AppStore/promotional-text-1.4.txt @@ -0,0 +1,14 @@ +# 프로모션 텍스트 — 1.4 전용 (로케일별 170자 이내, 심사 없이 수시 수정 가능) +# ASC → 앱 → 해당 로케일 → '프로모션 텍스트' 란에 복사해 붙여넣기. +# 2026-08-12 기준 이용자 3명(개발자 본인 포함) 감사 멘트 + "직접 쓰다 보니 아쉬워서 +# 벌써 1.4" 스토리 — 버전 전용 문구라, 다음 버전에서 상시 문구(promotional-text.txt)로 +# 되돌리거나 새로 쓰면 된다. + +[ko] (154자) +저를 포함해 세 분이 하루 다님과 함께하고 계세요. 어디선가 함께 걸어 주시는 두 분, 진심으로 감사해요. 매일 직접 쓰다 보니 아쉬운 곳이 보여 벌써 1.4로 돌아왔어요 — 지난 7일·30일 통계, 데이터 정리와 정리 리포트, 더 부드러운 펜슬 필기, iOS 18 지원까지. + +[en] (169자) +Three of us use Haru Danim — me included. To the two of you out there: thank you. 1.4 came quickly — last 7/30-day stats, data cleanup & report, smoother Pencil, iOS 18. + +[ja] (139자) +今日、私を含めて3人がHaru Danimと歩んでいます。どこかで一緒に歩いてくださるお二人、本当にありがとうございます。毎日使う中で気になる所があり、もう1.4になりました — 過去7日・30日の統計、データ整理と整理レポート、より滑らかなペンシル筆記、iOS 18対応まで。 diff --git a/myApp/HaruDanim/Marketing/AppStore/whats-new-1.4.txt b/myApp/HaruDanim/Marketing/AppStore/whats-new-1.4.txt index ff68e2d..6cda7f3 100644 --- a/myApp/HaruDanim/Marketing/AppStore/whats-new-1.4.txt +++ b/myApp/HaruDanim/Marketing/AppStore/whats-new-1.4.txt @@ -3,7 +3,7 @@ [ko] • 이제 iOS 18부터 쓸 수 있어요 — iPhone XS·XR과 구형 아이패드에서도 하루 다님을 만나 보세요 • 통계에 '지난 7일/30일 단위로 보기'가 생겼어요 — 주초·월초에 그래프가 허전할 때, 오늘로 끝나는 최근 7일·30일로 볼 수 있어요. 통계 위젯에도 같은 기간이 추가됐어요 -• 데이터 정리·초기화가 생겼어요 — 오래 쌓인 기록을 전체 또는 지정한 날짜까지 비울 수 있어요. 삭제 전에 CSV 백업과, 그동안의 기록을 한눈에 담은 '정리 리포트'(PDF·이미지)를 만들어 보관하세요 +• 데이터 정리·초기화가 생겼어요 — 오래 쌓인 기록을 전체 또는 지정한 날짜까지 비울 수 있고, 사진·필기로 커진 일기만 골라 지울 수도 있어요. 삭제 전에 CSV 백업과, 그동안의 기록을 한눈에 담은 '정리 리포트'(PDF·이미지)를 만들어 보관하세요 • 데이터 내보내기에 구간 지정이 생겼고, 일기도 내보낸 뒤 바로 삭제해 저장 공간을 줄일 수 있어요 • 애플 펜슬 필기가 한층 부드러워졌어요 — 오래 쓴 페이지에서 획마다 걸리던 저장 부하를 없앴어요