diff --git a/myApp/HaruDanim/CLAUDE.md b/myApp/HaruDanim/CLAUDE.md index fde3a91..26f2baf 100644 --- a/myApp/HaruDanim/CLAUDE.md +++ b/myApp/HaruDanim/CLAUDE.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 — 타이틀+기간(첫~마지막 기록일)+히어로+행동별 누적 표+꼬리표 합계+월별 표+목표 이력을 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` 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) - 프리미엄 화면(§8) / 도움말(`HelpView` — 기기별 주제 분기, DisclosureGroup. **그룹당 대표 주제 1개에 예시 스크린샷**(IOS/HelpImages/help-<이름>-.jpg, 총 18장 ≈1.6MB) — 언어별 파일을 HelpImage 로더가 표시 언어로 선택(jpg라 UIImage(named:) 대신 URL 로드). 앱 화면·문구가 크게 바뀌면 해당 스크린샷도 재촬영할 것: 시뮬 로케일별 재시드 후 §14 인자로 촬영, 워치는 상태바 크롭) / 버전(번들 CFBundleShortVersionString 표시) --- diff --git a/myApp/HaruDanim/IOS/Core/ArchiveReport.swift b/myApp/HaruDanim/IOS/Core/ArchiveReport.swift index eb6ed29..73b916c 100644 --- a/myApp/HaruDanim/IOS/Core/ArchiveReport.swift +++ b/myApp/HaruDanim/IOS/Core/ArchiveReport.swift @@ -313,6 +313,49 @@ enum ArchiveReport { )) } + // 목표별 다짐 표 — CSV에만 있던 다짐 구성(대상·주기·목표량)을 리포트에도 담는다 + // (그룹 = 목표, 목표량 표기는 앱 용어 그대로 "3회 이상 달성"/"1시간 이하 유지") + var questGroups: [ExportTableData.Group] = [] + for goal in goals { + let rows = goal.sortedQuests.map { quest in + ExportTableData.Row( + symbol: quest.targetSymbol, color: quest.targetColor, name: quest.targetName, + values: [quest.scheduleLabel, + "\(quest.targetValueLabel) \(quest.direction.label)"] + ) + } + guard !rows.isEmpty else { continue } + questGroups.append(.init(label: goal.title, rows: rows)) + } + // 페이지당 rowsPerPage행 기준으로 '그룹(목표) 단위' 분할 — 한 목표의 다짐들이 + // 페이지에 걸쳐 찢어지지 않게 한다 (한 그룹이 자체로 넘치면 그 그룹만 한 페이지) + var questPageGroups: [[ExportTableData.Group]] = [] + var currentGroups: [ExportTableData.Group] = [] + var currentRows = 0 + for group in questGroups { + if !currentGroups.isEmpty && currentRows + group.rows.count > rowsPerPage { + questPageGroups.append(currentGroups) + currentGroups = [] + currentRows = 0 + } + currentGroups.append(group) + currentRows += group.rows.count + } + if !currentGroups.isEmpty { questPageGroups.append(currentGroups) } + for (index, groups) in questPageGroups.enumerated() { + let suffix = index == 0 ? "" : String(localized: " (계속)") + let title = String(localized: "목표별 다짐") + suffix + pages.append(ExportSnapshot( + title: title, periodLabel: periodLabel, filterNote: nil, + hero: [], + sections: [.table(title: title, ExportTableData( + columns: [String(localized: "주기"), String(localized: "목표량")], + groups: groups + ))], + fileSlug: slug + )) + } + return pages } diff --git a/myApp/HaruDanim/IOS/Localizable.xcstrings b/myApp/HaruDanim/IOS/Localizable.xcstrings index 361a8fb..384b519 100644 --- a/myApp/HaruDanim/IOS/Localizable.xcstrings +++ b/myApp/HaruDanim/IOS/Localizable.xcstrings @@ -6182,6 +6182,22 @@ } } }, + "목표별 다짐": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Quests per Goal" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "目標別クエスト" + } + } + } + }, "목표에 속한 다짐들의 진행률을 원형 링으로 한눈에 봐요. (프리미엄)": { "localizations": { "en": {