diff --git a/myApp/HaruDanim/CLAUDE.md b/myApp/HaruDanim/CLAUDE.md index 158ccc8..42b4da8 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-22) | **버전 1.5 (빌드 4) — 소킹 첫날 사용자 요구 5건 추가 구현 완료(2026-08-22 저녁, 아래 '1.5 추가분'), TestFlight 재업로드 대기**. ⚠️**미해결 실측 이슈 1건(사용자 재현)**: 일기에 도형·텍스트 상자(DiaryPageItem)가 있으면 **그 기기의 CloudKit 동기화 전체가 정지**(내보내기 대기열이 직렬이라 거부된 아이템 뒤로 모든 내보내기·가져오기가 막힘 — 아이템 삭제 시 즉시 회복도 재현됨). 추정 원인: 오늘 프로덕션 배포가 1.0 시절 개발 스키마 기준이라 **1.3의 DiaryPageItem 스타일 필드(text·fontSize·textBold·textAlignRaw·textBorder·borderColorHex·heightRatio·lineWidth·filled·cornerRadius)가 여전히 프로덕션에 없음**. 해결 진행: **①개발 스키마 필드 생성 완료(2026-08-22 22:00 — 시뮬 519869F7에서 일기 도형·텍스트 상자 시드 포함 전체 내보내기 success·error nil. 시뮬 iCloud는 재인증 필요했음[CKAccountStatusTemporarilyUnavailable → 사용자 암호 입력 후 성공])** → ②남은 것: 사용자가 콘솔에서 **Deploy Schema Changes 재배포**(diff에 DiaryPageItem 필드 확인) → ③아이패드 앱 재시작으로 자동 회복 확인(밀린 내보내기 재시도 — 도형 있는 상태로 동기화 왕복 검증). 업로드 검증 실측 2건: 빌드 1=ITMS-90683(HealthKit 엔타이틀먼트 보유 시 읽기 전용이어도 `NSHealthUpdateUsageDescription` 필수 — 쓰기 사유 3언어 추가, 쓰기 요청이 없어 표시될 일 없음), 빌드 2=ITMS-90626(**앱 인텐트 메타데이터에 'iPhone' 단어 금지** — 신규 컴플리케이션 인텐트·위젯 설명에서 '하루 다님 앱'으로 교체. 화면 뷰 문구의 iPhone은 스캔 대상 아님[1.4 전례]) → **빌드 3**으로 재업로드. **1.5 추가분(빌드 4, 2026-08-22 — 소킹 중 사용자 요구, 스키마 무변경·전부 로컬 설정/표시 계층)**: ⑦**아이폰 일기 탭 개방**(§6.7 — AppTab.phoneCases 개방[radialOrder 필터 제거·버블 8개 자동 배치 확인], 노트 필기는 맥 '그리기' 토글 문법 재사용[DeviceLayout.isPhone 신설, drawingPolicy anyInput, 가로 스크롤 도구 줄, 배치↔그리기 상호 배타 — 아이패드 경로 무변경], 일기 잠금 설정 전 기기 노출) ⑧**일기 하루 정리 '건강 데이터' 카드**(§6.7 — DiarySection.health[케이스 마지막이라 기존 사용자 목록 끝에 추가], DiaryHealthCard: 지표 선택 시트 공용화[HealthMetricPickerSheet Binding화], 저장은 기기 로컬 diary.healthMetrics[비면 타일 선택 상속], 맥은 목록·화면 모두 숨김, 인쇄 요약에 값 줄 DiaryPrintHealthRow 동승) ⑨**일기 타임테이블 수면·운동 실구간 블록**(§6.7 — **HealthIntervals 신설**[App Group health.intervals.v1, 400일, 실구간을 논리적 하루로 분할 저장·병합, CloudKit 금지 동일], refreshRecent/백필이 유지, ExportSnapshot.injectingHealthBlocks 주입[캘린더 전례·최하층 렌더 healthBlockView 반투명+실선], 지표 고정색 수면 인디고·운동 주황, 필터 시트 '건강 데이터' 토글[RecordFilterSheet showsHealthSection — 전 날짜 공통 diary.timetableHealthHidden, 기본 켬]+**맥 안내 문구**, 일기 필터 버튼 상시 노출로 변경, 내보내기 렌더러 동일 주입) ⑩**수면 표시 구간 설정**(§6.8 — SleepWindowPrefs[LocalPrefs]: 전체 스펙+요일별 7종[켤 때만], 설정 → 건강 데이터 → '수면 표시 구간'[SleepWindowSettingsView — 시작=끝이면 24시간 창], 평문 'sleep' 캐시 키=그날 유효 구간 값으로 변경[타일·일기 카드 공유], 백필 서명에 요일 매핑 포함[배정만 바뀌어도 재백필], **새 수면 다짐 기본 구간을 여기서 시드**) ⑪일기 목표 카드의 건강 다짐 값 단위 실측 수정("8,432회/8,000보"→지표 단위 — DiaryGoalsCard.valueLabel healthTarget 분기). 검증: 61/20/27/10 ALL PASS(26.5), 3종 빌드 그린, 전 카탈로그 missing/stale 0, 시각 QA(아이폰 요약·건강 카드·타임테이블 블록·노트 도구 줄·버블 8개·수면 구간 화면·아이패드·맥 분기·18.5). 스토어 자료: whats-new-1.5에 일기 확장 절 추가·설명 3언어 일기 항목 갱신. 신규 인자 `-sleepWindowPreview`(§14), `-seedHealthCache`가 실구간(수면 자정 분할+달리기)도 시드. 계획·결정 이력은 `Docs/plan-1.5.md`(전 섹션 ✅구현 완료). 1.5 내용: ①**워치 '행동 기록' 컴플리케이션**(§11 — 행동 편집 옵트인+표시 기간 5종[하루/이번 주/이번 달/지난 7일/지난 30일, 롤링은 rollingRange 재사용], 측정 중 노란 타이머, 탭=워치 앱 미니 행동 화면 push[뒤로가기=메인], recommendations 상한 없음) ②**모음 탭 건강 데이터 타일**(무료, §6.1 — 걸음·운동·수면 등 8지표 선택·순서, 접기, 연결 CTA[명시적 탭 권한], 안내 시트, 즐겨찾기·나머지·건강 3섹션 순서 설정[§6.8], 맥은 섹션 숨김) ③**건강 다짐**(프리미엄, §4.2·§6.4 — 8지표+운동 종목 20종[workout. 인코딩], 수면은 다짐별 수면 구간[기본 21:00~09:00]을 '구간 끝이 속한 논리적 하루'에 통귀속[겹침 분할과 다른 의도된 예외], 하루 단위 전용·마감 시각 미지원, QuestProgress 값 공급 단일 지점 3곳만 분기[수학 무변경 — 기존 49건 그린이 증거], HealthCache[App Group, 400일, ⚠️5.1.3: 값은 CloudKit 저장 금지 — 정의만 동기화]·백필·부트스트랩, 데이터 없는 기기는 모수 제외+'데이터 없음' 표기+판정 보류, 구버전 이중 상태는 행동/꼬리표 우선 정규화) ④**신기능 소개 팝업**(WhatsNewView — 버전당 1회, 신규 설치 시드로 미표시, 온보딩 우선, 全기기 업데이트 안내) ⑤**도움말 전면 자연화+신규 주제 3개**(사용자 예약 작업 — 오타·번역투·UI 명칭 정정, en/ja 전면 재번역·용어 통일[Action/アクション 등]) ⑥HealthKit 엔타이틀먼트(iOS 앱만)+NSHealthShareUsageDescription 3언어+PRIVACY.md 건강 문단. 검증: `-progressSelfTest` **61건**(건강 H 시리즈 12 신규)·20·27·10 ALL PASS(26.5+18.5 심), Debug/Store/워치(26.2·11.5) 빌드, 시각 QA 20여 장(워치 컴플리케이션 실데이터·미니 화면·타일·다짐 행·편집기·팝업·ja 도움말), 실버전 업그레이드 팝업 경로·벌크 1만 건+건강 결합 확인. 스토어 자료: whats-new-1.5·promotional-text-1.5·설명 3언어 갱신, **스크린샷 전면 신규 — 아이폰 10·아이패드 10·워치 1 × ko/en/ja 원본 57장+합성 63장 재생성 완료**(09-records→09-health 교체, Marketing/README 절차·주의 갱신). **CloudKit 프로덕션 스키마 배포 완료(2026-08-22, 사용자 콘솔 실행)** — Quest 신규 필드 3종(healthMetricRaw·sleepWindow*)은 개발 스키마에 없어서(디버그 내보내기 이력 없음이 원인) 시뮬 519869F7에 사용자 iCloud 로그인 후 디버그 빌드를 `-premium YES -cloudSync YES -seedHealthQuest YES`로 실행 → Sandbox 환경 전체 내보내기 성공(98레코드, error nil)으로 필드 생성 → 콘솔 배포(이 시뮬은 iCloud 로그인 유지 — 향후 스키마 작업 재사용 가능). **실측 발견: 1.3의 DiaryPageItem 스타일 필드가 프로덕션 미배포 상태였음**(마지막 배포=1.0 출시 시점 — 1.3 출시[8/6] 이후 도형·텍스트 상자 신규 스타일 필드 동기화가 조용히 실패했을 가능성, 이번 배포로 해소·밀린 내보내기는 자동 재시도. 소킹 중 아이패드↔맥 요소 동기화 교차 확인 권장). **남은 출시 절차(사용자)**: ①TestFlight 실기기 소킹(진행 중 — 실제 건강 데이터·워치 컴플리케이션·기기 간 건강 다짐 정의 동기화 중점) ②ASC 심사 제출(스크린샷 framed 세트 교체·whats-new/프로모션/설명 반영). **1.6 후보 보류 기록: `Docs/plan-1.6.md`**(타임테이블 건강 데이터 표시 — 2026-08-22 사용자 "일단 보류", 구현 금지·재논의 시 착수). 운동 종목 목록(20종)은 §3.4 — 사용자 실사용 종목 빠졌으면 추가 요청. **버전 1.4 (빌드 3) App Store 출시 완료(2026-08-13, 사용자 확인)** — 2026-08-13 심사 제출 후 같은 날 출시 확인. TestFlight 실기기 테스트(문제 없음 확인) 후 제출했음. iOS 18 실기기는 미확보로 미테스트(시뮬 검증 106건 ALL PASS로 갈음 — 해당 사용자 유입 시 피드백 대응 예정). 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.4(3)에서 한국어 문장 흐름 자연화(사용자 피드백 — 설명 ko 자연화+3언어에 1.4 기능 2줄 추가, whats-new 전면 재작성, 프로모션 1.4 전용 `promotional-text-1.4.txt`). **📌 다음 버전(1.5) 확정 계획 — 상세는 `Docs/plan-1.5.md`(2026-08-20 사용자 결정 완료)**: ①워치 행동 컴플리케이션(행동 편집에서 옵트인+기간 선택[하루/이번 주/이번 달/지난 7일/지난 30일], 탭=미니 행동 화면 착지·뒤로가기로 메인 복귀) ②건강 데이터 통합(모음 탭 타일 **무료**+건강 다짐 **프리미엄**, HealthCache 구조[값은 CloudKit 저장 금지 — 5.1.3], 수면은 사용자 지정 수면 구간→깨어난 날 귀속, 모음 탭 3섹션[즐겨찾기·나머지·건강] 순서 설정) ③도움말(HelpView) 전체 문구 한국어 기준 자연화(사용자 지시 2026-08-12 — ~해요체 유지·번역투 제거·en/ja 재검토·l10n 루틴) ④업데이트 신기능 소개 팝업(버전당 1회, 全기기 업데이트 안내 — 계획서 §11). 운동 세부 종목 다짐 포함(§3.4), 스크린샷 전면 신규(아이폰·아이패드 각 10장 — §6), 모든 사전 결정 완료(결정 이력 8건·위험 검토 R1~R16). 하위 OS 인하는 검토 후 기각(근거는 계획서 §4 — 재제안 금지). ⚠️**구현은 사용자의 "진행/시작" 지시가 있을 때만 착수** — 그 전까지 코드 무변경, 추가 요구는 계획서에 계속 반영. **버전 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-22) | **버전 1.5 (빌드 4) — 소킹 첫날 사용자 요구 5건 추가 구현 완료(2026-08-22 저녁, 아래 '1.5 추가분'), TestFlight 재업로드 대기**. ⚠️**미해결 실측 이슈 1건(사용자 재현)**: 일기에 도형·텍스트 상자(DiaryPageItem)가 있으면 **그 기기의 CloudKit 동기화 전체가 정지**(내보내기 대기열이 직렬이라 거부된 아이템 뒤로 모든 내보내기·가져오기가 막힘 — 아이템 삭제 시 즉시 회복도 재현됨). 추정 원인: 오늘 프로덕션 배포가 1.0 시절 개발 스키마 기준이라 **1.3의 DiaryPageItem 스타일 필드(text·fontSize·textBold·textAlignRaw·textBorder·borderColorHex·heightRatio·lineWidth·filled·cornerRadius)가 여전히 프로덕션에 없음**. 해결 진행: **①개발 스키마 필드 생성 완료(2026-08-22 22:00 — 시뮬 519869F7에서 일기 도형·텍스트 상자 시드 포함 전체 내보내기 success·error nil. 시뮬 iCloud는 재인증 필요했음[CKAccountStatusTemporarilyUnavailable → 사용자 암호 입력 후 성공])** → ②남은 것: 사용자가 콘솔에서 **Deploy Schema Changes 재배포**(diff에 DiaryPageItem 필드 확인) → ③아이패드 앱 재시작으로 자동 회복 확인(밀린 내보내기 재시도 — 도형 있는 상태로 동기화 왕복 검증). 업로드 검증 실측 2건: 빌드 1=ITMS-90683(HealthKit 엔타이틀먼트 보유 시 읽기 전용이어도 `NSHealthUpdateUsageDescription` 필수 — 쓰기 사유 3언어 추가, 쓰기 요청이 없어 표시될 일 없음), 빌드 2=ITMS-90626(**앱 인텐트 메타데이터에 'iPhone' 단어 금지** — 신규 컴플리케이션 인텐트·위젯 설명에서 '하루 다님 앱'으로 교체. 화면 뷰 문구의 iPhone은 스캔 대상 아님[1.4 전례]) → **빌드 3**으로 재업로드. **1.5 추가분(빌드 4, 2026-08-22 — 소킹 중 사용자 요구, 스키마 무변경·전부 로컬 설정/표시 계층)**: ⑦**아이폰 일기 탭 개방**(§6.7 — AppTab.phoneCases 개방[radialOrder 필터 제거·버블 8개 자동 배치 확인], 노트 필기는 맥 '그리기' 토글 문법 재사용[DeviceLayout.isPhone 신설, drawingPolicy anyInput, 가로 스크롤 도구 줄, 배치↔그리기 상호 배타 — 아이패드 경로 무변경], 일기 잠금 설정 전 기기 노출) ⑧**일기 하루 정리 '건강 데이터' 카드**(§6.7 — DiarySection.health[케이스 마지막이라 기존 사용자 목록 끝에 추가], DiaryHealthCard: 지표 선택 시트 공용화[HealthMetricPickerSheet Binding화], 저장은 기기 로컬 diary.healthMetrics[비면 타일 선택 상속], 맥은 목록·화면 모두 숨김, 인쇄 요약에 값 줄 DiaryPrintHealthRow 동승) ⑨**일기 타임테이블 수면·운동 실구간 블록**(§6.7 — **HealthIntervals 신설**[App Group health.intervals.v1, 400일, 실구간을 논리적 하루로 분할 저장·병합, CloudKit 금지 동일], refreshRecent/백필이 유지, ExportSnapshot.injectingHealthBlocks 주입[캘린더 전례·최하층 렌더 healthBlockView 반투명+실선], 지표 고정색 수면 인디고·운동 주황, 필터 시트 '건강 데이터' 토글[RecordFilterSheet showsHealthSection — 전 날짜 공통 diary.timetableHealthHidden, 기본 켬]+**맥 안내 문구**, 일기 필터 버튼 상시 노출로 변경, 내보내기 렌더러 동일 주입) ⑩**수면 표시 구간 설정**(§6.8 — SleepWindowPrefs[LocalPrefs]: 전체 스펙+요일별 7종[켤 때만], 설정 → 건강 데이터 → '수면 표시 구간'[SleepWindowSettingsView — 시작=끝이면 24시간 창], 평문 'sleep' 캐시 키=그날 유효 구간 값으로 변경[타일·일기 카드 공유], 백필 서명에 요일 매핑 포함[배정만 바뀌어도 재백필], **새 수면 다짐 기본 구간을 여기서 시드**) ⑪일기 목표 카드의 건강 다짐 값 단위 실측 수정("8,432회/8,000보"→지표 단위 — DiaryGoalsCard.valueLabel healthTarget 분기) ⑫**소킹 실기기 실측 버그 2건 수정(빌드 5)**: ⓐ더보기 탭 안 일기 달력에서 날짜 탭이 조용히 무시됨 — `AppRouter.morePath`가 `[AppTab]` 고정 타입이라 Date 푸시 불가 → **NavigationPath로 변경**(회귀 감시 인자 `-morePushDiaryToday`, 라디얼·사이드바는 원래 무사) ⓑ수면 블록이 수면 단계(코어·렘) 로우데이터대로 칸칸이 갈라짐 → **10분 이하의 깸은 이어 붙여 한 덩어리로**(DiaryHealthTimetable.sleepDisplayMergeGap — 표시 전용, 값 무영향. 20분쯤 깼다 자면 나뉘는 게 맞다는 사용자 규칙) ⑬**기록 탭 타임테이블에도 수면·운동 블록**(하루·주간 — DiaryHealthTimetable.blocks 단일 공급 지점·숨김 토글 공유[기록 필터 시트에도 건강 섹션], 내보내기는 `injectingHealthBlocks(forDayKeys:math:)`로 하루·주간 주입 + trim 표시 범위를 블록만큼 확장) ⑭**타임테이블 표시 색 설정**(설정 → 건강 데이터 → '타임테이블 표시 색' — 수면·운동 ColorPicker, 기본 인디고·주황[빈 값], 전 타임테이블·내보내기 공통 적용, 인자 `-healthColorPreview`). 검증: 61/20/27/10 ALL PASS(26.5), 3종 빌드 그린, 전 카탈로그 missing/stale 0, 시각 QA(아이폰 요약·건강 카드·타임테이블 블록·노트 도구 줄·버블 8개·수면 구간 화면·아이패드·맥 분기·18.5). 스토어 자료: whats-new-1.5에 일기 확장 절 추가·설명 3언어 일기 항목 갱신. 신규 인자 `-sleepWindowPreview`(§14), `-seedHealthCache`가 실구간(수면 자정 분할+달리기)도 시드. 계획·결정 이력은 `Docs/plan-1.5.md`(전 섹션 ✅구현 완료). 1.5 내용: ①**워치 '행동 기록' 컴플리케이션**(§11 — 행동 편집 옵트인+표시 기간 5종[하루/이번 주/이번 달/지난 7일/지난 30일, 롤링은 rollingRange 재사용], 측정 중 노란 타이머, 탭=워치 앱 미니 행동 화면 push[뒤로가기=메인], recommendations 상한 없음) ②**모음 탭 건강 데이터 타일**(무료, §6.1 — 걸음·운동·수면 등 8지표 선택·순서, 접기, 연결 CTA[명시적 탭 권한], 안내 시트, 즐겨찾기·나머지·건강 3섹션 순서 설정[§6.8], 맥은 섹션 숨김) ③**건강 다짐**(프리미엄, §4.2·§6.4 — 8지표+운동 종목 20종[workout. 인코딩], 수면은 다짐별 수면 구간[기본 21:00~09:00]을 '구간 끝이 속한 논리적 하루'에 통귀속[겹침 분할과 다른 의도된 예외], 하루 단위 전용·마감 시각 미지원, QuestProgress 값 공급 단일 지점 3곳만 분기[수학 무변경 — 기존 49건 그린이 증거], HealthCache[App Group, 400일, ⚠️5.1.3: 값은 CloudKit 저장 금지 — 정의만 동기화]·백필·부트스트랩, 데이터 없는 기기는 모수 제외+'데이터 없음' 표기+판정 보류, 구버전 이중 상태는 행동/꼬리표 우선 정규화) ④**신기능 소개 팝업**(WhatsNewView — 버전당 1회, 신규 설치 시드로 미표시, 온보딩 우선, 全기기 업데이트 안내) ⑤**도움말 전면 자연화+신규 주제 3개**(사용자 예약 작업 — 오타·번역투·UI 명칭 정정, en/ja 전면 재번역·용어 통일[Action/アクション 등]) ⑥HealthKit 엔타이틀먼트(iOS 앱만)+NSHealthShareUsageDescription 3언어+PRIVACY.md 건강 문단. 검증: `-progressSelfTest` **61건**(건강 H 시리즈 12 신규)·20·27·10 ALL PASS(26.5+18.5 심), Debug/Store/워치(26.2·11.5) 빌드, 시각 QA 20여 장(워치 컴플리케이션 실데이터·미니 화면·타일·다짐 행·편집기·팝업·ja 도움말), 실버전 업그레이드 팝업 경로·벌크 1만 건+건강 결합 확인. 스토어 자료: whats-new-1.5·promotional-text-1.5·설명 3언어 갱신, **스크린샷 전면 신규 — 아이폰 10·아이패드 10·워치 1 × ko/en/ja 원본 57장+합성 63장 재생성 완료**(09-records→09-health 교체, Marketing/README 절차·주의 갱신). **CloudKit 프로덕션 스키마 배포 완료(2026-08-22, 사용자 콘솔 실행)** — Quest 신규 필드 3종(healthMetricRaw·sleepWindow*)은 개발 스키마에 없어서(디버그 내보내기 이력 없음이 원인) 시뮬 519869F7에 사용자 iCloud 로그인 후 디버그 빌드를 `-premium YES -cloudSync YES -seedHealthQuest YES`로 실행 → Sandbox 환경 전체 내보내기 성공(98레코드, error nil)으로 필드 생성 → 콘솔 배포(이 시뮬은 iCloud 로그인 유지 — 향후 스키마 작업 재사용 가능). **실측 발견: 1.3의 DiaryPageItem 스타일 필드가 프로덕션 미배포 상태였음**(마지막 배포=1.0 출시 시점 — 1.3 출시[8/6] 이후 도형·텍스트 상자 신규 스타일 필드 동기화가 조용히 실패했을 가능성, 이번 배포로 해소·밀린 내보내기는 자동 재시도. 소킹 중 아이패드↔맥 요소 동기화 교차 확인 권장). **남은 출시 절차(사용자)**: ①TestFlight 실기기 소킹(진행 중 — 실제 건강 데이터·워치 컴플리케이션·기기 간 건강 다짐 정의 동기화 중점) ②ASC 심사 제출(스크린샷 framed 세트 교체·whats-new/프로모션/설명 반영). **1.6 후보 보류 기록: `Docs/plan-1.6.md`**(타임테이블 건강 데이터 표시 — 2026-08-22 사용자 "일단 보류", 구현 금지·재논의 시 착수). 운동 종목 목록(20종)은 §3.4 — 사용자 실사용 종목 빠졌으면 추가 요청. **버전 1.4 (빌드 3) App Store 출시 완료(2026-08-13, 사용자 확인)** — 2026-08-13 심사 제출 후 같은 날 출시 확인. TestFlight 실기기 테스트(문제 없음 확인) 후 제출했음. iOS 18 실기기는 미확보로 미테스트(시뮬 검증 106건 ALL PASS로 갈음 — 해당 사용자 유입 시 피드백 대응 예정). 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.4(3)에서 한국어 문장 흐름 자연화(사용자 피드백 — 설명 ko 자연화+3언어에 1.4 기능 2줄 추가, whats-new 전면 재작성, 프로모션 1.4 전용 `promotional-text-1.4.txt`). **📌 다음 버전(1.5) 확정 계획 — 상세는 `Docs/plan-1.5.md`(2026-08-20 사용자 결정 완료)**: ①워치 행동 컴플리케이션(행동 편집에서 옵트인+기간 선택[하루/이번 주/이번 달/지난 7일/지난 30일], 탭=미니 행동 화면 착지·뒤로가기로 메인 복귀) ②건강 데이터 통합(모음 탭 타일 **무료**+건강 다짐 **프리미엄**, HealthCache 구조[값은 CloudKit 저장 금지 — 5.1.3], 수면은 사용자 지정 수면 구간→깨어난 날 귀속, 모음 탭 3섹션[즐겨찾기·나머지·건강] 순서 설정) ③도움말(HelpView) 전체 문구 한국어 기준 자연화(사용자 지시 2026-08-12 — ~해요체 유지·번역투 제거·en/ja 재검토·l10n 루틴) ④업데이트 신기능 소개 팝업(버전당 1회, 全기기 업데이트 안내 — 계획서 §11). 운동 세부 종목 다짐 포함(§3.4), 스크린샷 전면 신규(아이폰·아이패드 각 10장 — §6), 모든 사전 결정 완료(결정 이력 8건·위험 검토 R1~R16). 하위 OS 인하는 검토 후 기각(근거는 계획서 §4 — 재제안 금지). ⚠️**구현은 사용자의 "진행/시작" 지시가 있을 때만 착수** — 그 전까지 코드 무변경, 추가 요구는 계획서에 계속 반영. **버전 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 체크리스트 | --- @@ -355,7 +355,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`(다짐 편집기 표시 — 마감 시각 다짐 우선, 없으면 첫 다짐. `-questShowEditorHealth`를 함께 주면 건강 다짐 우선 — 1.5) `-endGoalYesterday "제목"`(종료일을 어제로 — 실행 시점 자동 판정 검증) `-watchGoals `(생성순 목표 N개를 '애플워치에서 보기'로 설정, 0=전체 해제 — 설정 후 스냅숏 즉시 푸시, 컴플리케이션 필터 검증용) `-watchActions `(1.5 — 배치순 행동 N개를 '행동 기록' 컴플리케이션에 올림. 표시 기간은 하루→이번 주→이번 달→지난 7일→지난 30일 순환, 스냅숏 즉시 푸시) `-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 — 인메모리 컨테이너라 실데이터 무영향) `-showWhatsNew`(1.5 — 신기능 소개 팝업 강제 표시) `-dataResetPreview`(데이터 정리·초기화 화면 직접 표시 — 1.4. +`-dataResetBackupRun pdf|image`면 백업 3종 자동 생성 후 Documents 복사, `-dataResetRangeDays `이면 날짜 지정[오늘-N일까지], `-dataResetScope diary`면 '일기만' 선택으로 시작) `-dataResetTest`(초기화 자가 검증 27건[범위 삭제 경계·보존·cascade·전체 초기화·일기만] → Documents/data-reset-test.txt — 인메모리·설정 고정, 실데이터 무영향) `-seedHealthCache`(1.5 — 건강 스토어를 합성 값으로 대체[HK 비의존]·과거 7일+운동 종목·수면 구간 키까지 캐시 기록, 건강 타일·다짐 렌더 검증용) `-seedHealthQuest`(1.5 — 건강 다짐 목표 1개 시드: 걸음 8000·수면 8h·달리기 30분·물 2L, 멱등) `-healthQuestDump`(1.5 — 건강 다짐들의 캐시 키·오늘 값·하루 진행률 → Documents/health-quest-dump.txt) `-healthShowGuide`(1.5 — 건강 안내 시트 표시) `-healthShowMetricPicker`(1.5 — 건강 지표 선택 시트 표시) `-sleepWindowPreview`(1.5(4) — 수면 표시 구간 설정 화면 직접 표시) `-symbolAuditDump`(아이콘 카탈로그 중 현재 OS에 없는 심볼 → Documents/symbol-audit.txt — 하위 OS QA, 1.4) `-actionShowFavoriteOrder`(행동 탭 즐겨찾기 순서 시트 — `-startTab action`과 함께) `-actionShowEditor`(1.5 — 첫 행동의 편집 시트를 열고 애플워치 섹션까지 자동 스크롤, `-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 — 인메모리 컨테이너라 실데이터 무영향) `-showWhatsNew`(1.5 — 신기능 소개 팝업 강제 표시) `-dataResetPreview`(데이터 정리·초기화 화면 직접 표시 — 1.4. +`-dataResetBackupRun pdf|image`면 백업 3종 자동 생성 후 Documents 복사, `-dataResetRangeDays `이면 날짜 지정[오늘-N일까지], `-dataResetScope diary`면 '일기만' 선택으로 시작) `-dataResetTest`(초기화 자가 검증 27건[범위 삭제 경계·보존·cascade·전체 초기화·일기만] → Documents/data-reset-test.txt — 인메모리·설정 고정, 실데이터 무영향) `-seedHealthCache`(1.5 — 건강 스토어를 합성 값으로 대체[HK 비의존]·과거 7일+운동 종목·수면 구간 키까지 캐시 기록, 건강 타일·다짐 렌더 검증용) `-seedHealthQuest`(1.5 — 건강 다짐 목표 1개 시드: 걸음 8000·수면 8h·달리기 30분·물 2L, 멱등) `-healthQuestDump`(1.5 — 건강 다짐들의 캐시 키·오늘 값·하루 진행률 → Documents/health-quest-dump.txt) `-healthShowGuide`(1.5 — 건강 안내 시트 표시) `-healthShowMetricPicker`(1.5 — 건강 지표 선택 시트 표시) `-sleepWindowPreview`(1.5(4) — 수면 표시 구간 설정 화면 직접 표시) `-healthColorPreview`(1.5(5) — 타임테이블 표시 색 설정 화면 직접 표시) `-morePushDiaryToday`(1.5(5) — 더보기 스택에 [일기, 오늘]을 프로그램 푸시: 이종 타입 Date 푸시 회귀 감시) `-symbolAuditDump`(아이콘 카탈로그 중 현재 OS에 없는 심볼 → Documents/symbol-audit.txt — 하위 OS QA, 1.4) `-actionShowFavoriteOrder`(행동 탭 즐겨찾기 순서 시트 — `-startTab action`과 함께) `-actionShowEditor`(1.5 — 첫 행동의 편집 시트를 열고 애플워치 섹션까지 자동 스크롤, `-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용) `-watchOpenActionDetail`(1.5 — 스냅숏 수신 후 첫 옵트인 행동의 미니 행동 화면을 딥링크와 같은 경로로 push, 착지·뒤로가기 검증) | diff --git a/myApp/HaruDanim/Docs/plan-1.5.md b/myApp/HaruDanim/Docs/plan-1.5.md index 3b8fa13..805b8d5 100644 --- a/myApp/HaruDanim/Docs/plan-1.5.md +++ b/myApp/HaruDanim/Docs/plan-1.5.md @@ -170,3 +170,10 @@ 6. 부수 실측 수정: 일기 목표 카드 건강 다짐 값 단위("8,432회/8,000보" → 지표 단위). 검증: 자가 검증 61/20/27/10 ALL PASS, 3종 빌드 그린, 전 카탈로그 missing/stale 0, 시각 QA 10여 장(아이폰 요약·카드·블록·노트·필터·버블8·수면 구간, 아이패드, 맥 분기[카드 숨김], 18.5). 마케팅: 아이폰 06-bubble 재촬영(버블 8개)·10-week→**10-diary** 교체·아이패드 03-diary-summary 재촬영(건강 카드·블록), whats-new-1.5/설명 3언어 갱신. 빌드 4. + +### §12 후속 — 소킹 실기기 피드백 반영 (같은 날 밤, 빌드 5) + +- **버그 ⓐ**: 아이폰 더보기 탭 안 일기 달력에서 날짜를 눌러도 상세로 안 들어감(터치 반응만) — `AppRouter.morePath: [AppTab]`(고정 타입)에는 Date를 푸시할 수 없어 조용히 무시. → `NavigationPath`로 변경, 회귀 감시 인자 `-morePushDiaryToday`(더보기 스택에 [일기, 오늘] 프로그램 푸시 → 상세 착지 실측 확인). 라디얼·사이드바(비타입 스택)는 원래 정상 — "처음엔 됐다"는 증언과 일치. +- **버그 ⓑ**: 수면 블록이 수면 단계(코어·렘·깸) 로우데이터 그대로 칸칸이 갈라짐 — 표시 병합 규칙 신설: **10분 이하의 깸은 이어 붙임**(`sleepDisplayMergeGap`, mergedIntervals에 tolerance 파라미터). 사용자 규칙 "연속으로 잤으면 한 칸, 20분쯤 깼다 자면 분리". 값(합계)은 실제 잔 시간 그대로 무영향. +- **추가 요구 2건**: ①**타임테이블 표시 색 설정**(설정 → 건강 데이터 — 수면·운동 ColorPicker, 기본 인디고·주황=빈 값, App Group 기기 로컬, DiaryHealthTimetable.sleepColor/workoutColor 단일 지점이라 전 타임테이블·내보내기 자동 공통) ②**기록 탭 타임테이블에도 수면·운동 블록**(하루·주간 — 같은 공급 지점·같은 숨김 토글[기록 필터 시트에 건강 섹션 추가], 내보내기 `injectingHealthBlocks(forDayKeys:)` + trim 범위 확장. plan-1.6 후보 1의 잔여분 조기 흡수 — 1.6에 남은 것은 물 점 표시뿐). +- 도움말·whats-new·팝업 문구 갱신, l10n 0/0, 빌드 5. diff --git a/myApp/HaruDanim/Docs/plan-1.6.md b/myApp/HaruDanim/Docs/plan-1.6.md index 7c89388..c625de6 100644 --- a/myApp/HaruDanim/Docs/plan-1.6.md +++ b/myApp/HaruDanim/Docs/plan-1.6.md @@ -3,7 +3,7 @@ > **상태: 보류** — 사용자 결정(2026-08-22): "다음 업데이트 사항으로 두자. 생각이 바뀔 수도, 더 다양한 방법이 떠오를 수도 있으니 일단 보류." > ⚠️ **구현 금지** — 사용자가 재논의·진행을 지시할 때만 착수. 그 전까지 이 문서는 아이디어 기록 전용. > -> 📌 **같은 날 저녁 대부분 1.5로 조기 흡수됨**(사용자 재결정 — plan-1.5.md §12): 후보 1의 **일기 표면**(하루 정리 타임테이블 수면·운동 블록)과 후보 2(수면 표시 구간)는 **1.5(4)에 구현 완료**. 1.6에 남은 잔여분: **기록 탭 타임테이블에도 건강 블록 표시**(HealthIntervals 캐시·주입 문법·색은 이미 있어 표시 계층만 — 난이도 하), 물 점 표시(미구현 — 1.5(4)는 수면·운동만). +> 📌 **같은 날 저녁 대부분 1.5로 조기 흡수됨**(사용자 재결정 — plan-1.5.md §12): 후보 1의 **일기 표면**(하루 정리 타임테이블 수면·운동 블록)과 후보 2(수면 표시 구간)는 **1.5(4)에 구현 완료**. 1.6에 남은 잔여분: 물 점 표시뿐(1.5(4)는 수면·운동만 — 기록 탭 타임테이블 표시와 색 설정은 같은 날 밤 1.5(5)에 추가 흡수됨, plan-1.5.md §12 후속). ## 후보 1 — 타임테이블에 건강 데이터 표시 (일기 표면은 1.5(4)에 구현 — 잔여: 기록 탭) diff --git a/myApp/HaruDanim/Haru_Danim.xcodeproj/project.pbxproj b/myApp/HaruDanim/Haru_Danim.xcodeproj/project.pbxproj index a51e92d..3637548 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 = 4; + CURRENT_PROJECT_VERSION = 5; 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 = 4; + CURRENT_PROJECT_VERSION = 5; 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 = 4; + CURRENT_PROJECT_VERSION = 5; 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 = 4; + CURRENT_PROJECT_VERSION = 5; 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 = 4; + CURRENT_PROJECT_VERSION = 5; 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 = 4; + CURRENT_PROJECT_VERSION = 5; 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 = 4; + CURRENT_PROJECT_VERSION = 5; 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 = 4; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = 5GNS4Z2SYT; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Widgets/Info.plist; diff --git a/myApp/HaruDanim/IOS/ContentView.swift b/myApp/HaruDanim/IOS/ContentView.swift index 9bea4d0..1090b6a 100644 --- a/myApp/HaruDanim/IOS/ContentView.swift +++ b/myApp/HaruDanim/IOS/ContentView.swift @@ -40,6 +40,9 @@ struct ContentView: View { } else if UserDefaults.standard.bool(forKey: "sleepWindowPreview") { // 검증용: -sleepWindowPreview YES → 수면 표시 구간 설정 화면 직접 표시 (1.5 추가분) NavigationStack { SleepWindowSettingsView() } + } else if UserDefaults.standard.bool(forKey: "healthColorPreview") { + // 검증용: -healthColorPreview YES → 타임테이블 표시 색 설정 화면 직접 표시 (1.5(4)) + NavigationStack { HealthColorSettingsView() } } else { RootNavigationView() } @@ -233,7 +236,9 @@ final class AppRouter { var pendingStatsActionID: PersistentIdentifier? /// 더보기 탭 내부 내비게이션 경로. 더보기에 숨은 탭으로 바로 진입할 때 라우터가 채워 넣는다. - var morePath: [AppTab] = [] + /// ⚠️ 반드시 NavigationPath(이종 허용) — [AppTab] 고정 타입이면 더보기 안 화면이 다른 타입을 + /// 푸시하지 못한다(1.5(4) 실측: 일기 달력의 날짜(Date) 탭이 조용히 무시돼 상세로 못 들어감) + var morePath = NavigationPath() /// 기록 탭으로 이동하며 행동 필터를 예약. 기록 탭이 노출 탭에 없으면 더보기 안의 기록 화면까지 바로 진입. func openHistory(filtering actionID: PersistentIdentifier, visibleTabsRaw: String) { @@ -256,7 +261,7 @@ final class AppRouter { if visible.contains(tab) { tabSelection = tab.rawValue } else { - morePath = [tab] + morePath = NavigationPath([tab]) tabSelection = AppTab.moreTabValue } } @@ -387,11 +392,21 @@ struct MainTabView: View { } .onAppear { #if DEBUG + // 검증용: -morePushDiaryToday YES → 더보기 스택에 [일기, 오늘 날짜]를 프로그램 푸시. + // 일기 달력의 날짜 탭과 같은 '이종 타입(Date) 푸시' 경로 실측 — morePath가 [AppTab] + // 고정 타입이던 시절엔 이 푸시가 조용히 무시됐다(1.5(4) 수정의 회귀 감시 인자) + if UserDefaults.standard.bool(forKey: "morePushDiaryToday") { + var path = NavigationPath([AppTab.diary]) + path.append(DayMath().dayKey(for: .now)) + router.morePath = path + router.tabSelection = AppTab.moreTabValue + return + } // 검증용: -startTab이 노출 탭에 없으면 더보기 안의 그 화면까지 바로 진입 let valid = visibleTabs.map(\.rawValue) + [AppTab.moreTabValue] if !valid.contains(router.tabSelection) { if let hidden = AppTab(rawValue: router.tabSelection) { - router.morePath = [hidden] + router.morePath = NavigationPath([hidden]) } router.tabSelection = AppTab.moreTabValue } @@ -410,14 +425,7 @@ struct MoreTabView: View { @Bindable var router = router NavigationStack(path: $router.morePath) { List(tabs) { tab in - NavigationLink(value: tab) { - Label { - Text(tab.label) - } icon: { - Image(systemName: tab.symbol) - .foregroundStyle(AppTheme.green) - } - } + moreRow(tab) } .scrollContentBackground(.hidden) .background(AppTheme.background) @@ -427,6 +435,18 @@ struct MoreTabView: View { } } } + + @ViewBuilder + private func moreRow(_ tab: AppTab) -> some View { + NavigationLink(value: tab) { + Label { + Text(tab.label) + } icon: { + Image(systemName: tab.symbol) + .foregroundStyle(AppTheme.green) + } + } + } } #Preview { diff --git a/myApp/HaruDanim/IOS/Core/HealthStore.swift b/myApp/HaruDanim/IOS/Core/HealthStore.swift index 7228bdf..a98e5df 100644 --- a/myApp/HaruDanim/IOS/Core/HealthStore.swift +++ b/myApp/HaruDanim/IOS/Core/HealthStore.swift @@ -286,12 +286,14 @@ final class HealthDataStore { } /// 구간 합집합 (겹침 병합, 시작 오름차순) — mergedDuration의 구간 보존판. - /// 아이폰+워치가 같은 잠을 겹쳐 기록해도 타임테이블에 블록이 이중으로 그려지지 않게 한다 - static func mergedIntervals(_ intervals: [(Date, Date)]) -> [(Date, Date)] { + /// 아이폰+워치가 같은 잠을 겹쳐 기록해도 타임테이블에 블록이 이중으로 그려지지 않게 한다. + /// tolerance: 이 간격 이하로 떨어진 이웃 구간도 이어 붙인다 (표시 전용 — 값 합산에는 쓰지 않음) + static func mergedIntervals(_ intervals: [(Date, Date)], + tolerance: TimeInterval = 0) -> [(Date, Date)] { let sorted = intervals.sorted { $0.0 < $1.0 } var result: [(Date, Date)] = [] for (start, end) in sorted { - if let last = result.last, start <= last.1 { + if let last = result.last, start.timeIntervalSince(last.1) <= tolerance { result[result.count - 1].1 = max(last.1, end) } else { result.append((start, end)) diff --git a/myApp/HaruDanim/IOS/Localizable.xcstrings b/myApp/HaruDanim/IOS/Localizable.xcstrings index bf6e8d1..9b1c026 100644 --- a/myApp/HaruDanim/IOS/Localizable.xcstrings +++ b/myApp/HaruDanim/IOS/Localizable.xcstrings @@ -3626,6 +3626,22 @@ } } }, + "기본 색으로 되돌리기": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset to Default Colors" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "基本の色に戻す" + } + } + } + }, "기본 정보": { "localizations": { "en": { @@ -9897,18 +9913,18 @@ } } }, - "애플 건강의 수면·운동 시간을 실제 구간 그대로 타임테이블에 보여줘요. 이 설정은 모든 날짜에 함께 적용돼요.": { + "애플 건강의 수면·운동 시간을 실제 구간 그대로 타임테이블에 보여줘요. 이 설정은 일기·기록 탭의 모든 타임테이블에 함께 적용되고, 블록 색은 설정 → 건강 데이터에서 바꿀 수 있어요.": { "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Shows sleep and workout time from Apple Health on the timetable, at their actual times. This setting applies to all dates." + "value": "Shows sleep and workout time from Apple Health on the timetable, at their actual times. This setting applies to every timetable in the Diary and History tabs, and you can change the block colors in Settings → Health data." } }, "ja": { "stringUnit": { "state": "translated", - "value": "Appleヘルスケアの睡眠・運動時間を、実際の時間どおりタイムテーブルに表示します。この設定はすべての日付に適用されます。" + "value": "Appleヘルスケアの睡眠・運動時間を、実際の時間どおりタイムテーブルに表示します。この設定は日記・記録タブのすべてのタイムテーブルに適用され、ブロックの色は設定 → ヘルスデータで変えられます。" } } } @@ -12301,6 +12317,22 @@ } } }, + "일기·기록 탭 타임테이블에 깔리는 수면·운동 블록의 색이에요. 꼬리표 색과 비슷해서 헷갈리면 바꿔 보세요 — 모든 타임테이블과 내보내기에 함께 적용돼요.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The colors of the sleep and workout blocks on the Diary and History timetables. If they look too close to your tag colors, change them here — the change applies to every timetable and export." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "日記・記録タブのタイムテーブルに敷かれる睡眠・運動ブロックの色です。タグの色と紛らわしければここで変えてください — すべてのタイムテーブルと書き出しに適用されます。" + } + } + } + }, "일기가 잠겨 있어요": { "localizations": { "en": { @@ -12425,18 +12457,18 @@ } } }, - "일기의 하루 정리 첫 페이지에 '건강 데이터' 카드가 있어요 — 그날의 걸음·운동·수면 같은 값을 일기와 함께 봐요. 카드 오른쪽 위 버튼으로 보여줄 지표를 고르면 모든 날짜에 함께 적용돼요. 타임테이블에는 수면·운동이 실제 시간 그대로 옅은 블록으로 깔려요(수면 남보라·운동 주황) — 타임테이블 필터의 '건강 데이터' 토글로 켜고 끌 수 있어요.\n건강 값은 기기 안에만 있어서 카드의 값도 기기마다 다를 수 있어요 — 아이패드는 시스템의 건강 iCloud 동기화를 켠 경우에만 아이폰과 같은 값이 보이고, 맥에서는 카드와 블록이 보이지 않아요.": { + "일기의 하루 정리 첫 페이지에 '건강 데이터' 카드가 있어요 — 그날의 걸음·운동·수면 같은 값을 일기와 함께 봐요. 카드 오른쪽 위 버튼으로 보여줄 지표를 고르면 모든 날짜에 함께 적용돼요. 일기와 기록 탭의 타임테이블에는 수면·운동이 실제 시간 그대로 옅은 블록으로 깔려요 — 기본 색은 수면 남보라·운동 주황이고, 설정 → 건강 데이터 → '타임테이블 표시 색'에서 바꿀 수 있어요. 필터의 '건강 데이터' 토글로 켜고 끄면 두 타임테이블에 함께 적용돼요.\n건강 값은 기기 안에만 있어서 카드의 값도 기기마다 다를 수 있어요 — 아이패드는 시스템의 건강 iCloud 동기화를 켠 경우에만 아이폰과 같은 값이 보이고, 맥에서는 카드와 블록이 보이지 않아요.": { "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "The Diary's daily summary page has a 'Health data' card — see that day's steps, exercise, sleep and more alongside your diary. Pick which metrics to show with the button at the card's top right; the choice applies to all dates. The timetable shows sleep and workouts as light blocks at their actual times (indigo for sleep, orange for workouts) — toggle them with 'Health data' in the timetable filter.\nHealth values stay on each device, so the card's values can differ between devices — an iPad shows the same values as your iPhone only when the system's Health iCloud sync is on, and on the Mac the card and blocks don't appear." + "value": "The Diary's daily summary page has a 'Health data' card — see that day's steps, exercise, sleep and more alongside your diary. Pick which metrics to show with the button at the card's top right; the choice applies to all dates. The Diary and History timetables show sleep and workouts as light blocks at their actual times — indigo and orange by default, changeable in Settings → Health data → 'Timetable colors'. The 'Health data' toggle in the filter turns them on or off for both timetables.\nHealth values stay on each device, so the card's values can differ between devices — an iPad shows the same values as your iPhone only when the system's Health iCloud sync is on, and on the Mac the card and blocks don't appear." } }, "ja": { "stringUnit": { "state": "translated", - "value": "日記の1日のまとめページに「ヘルスデータ」カードがあります — その日の歩数・運動・睡眠などの値を日記と一緒に見られます。カード右上のボタンで表示する指標を選ぶと、すべての日付に適用されます。タイムテーブルには睡眠・運動が実際の時間どおり薄いブロックで敷かれます(睡眠は藍色・運動はオレンジ)— タイムテーブルフィルターの「ヘルスデータ」トグルでオン・オフできます。\nヘルスの値は端末の中だけにあるため、カードの値も端末ごとに異なることがあります — iPadはシステムのヘルスケアiCloud同期をオンにした場合のみiPhoneと同じ値になり、Macではカードとブロックは表示されません。" + "value": "日記の1日のまとめページに「ヘルスデータ」カードがあります — その日の歩数・運動・睡眠などの値を日記と一緒に見られます。カード右上のボタンで表示する指標を選ぶと、すべての日付に適用されます。日記・記録タブのタイムテーブルには睡眠・運動が実際の時間どおり薄いブロックで敷かれます — 基本の色は藍色とオレンジで、設定 → ヘルスデータ →「タイムテーブルの表示色」で変えられます。フィルターの「ヘルスデータ」トグルでオン・オフすると、両方のタイムテーブルに適用されます。\nヘルスの値は端末の中だけにあるため、カードの値も端末ごとに異なることがあります — iPadはシステムのヘルスケアiCloud同期をオンにした場合のみiPhoneと同じ値になり、Macではカードとブロックは表示されません。" } } } @@ -14276,18 +14308,18 @@ } } }, - "첫 페이지 타임테이블 카드의 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 보여줄 수 있어요 — 일정은 점선 테두리라 행동과 바로 구분돼요. 필터 버튼으로는 그날 기록된 행동 중 타임테이블에 올릴 것만 고르고, '이 날의 목표' 카드의 필터로는 보고 싶은 목표만 남겨요. 이 선택들은 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 담겨요.\n애플 건강의 수면·운동 시간도 실제 잔 시간·운동한 시간 그대로 옅은 블록으로 깔려요 — 수면은 남보라, 운동은 주황이에요. 필터의 '건강 데이터' 토글로 켜고 끌 수 있고(모든 날짜 공통), 건강 데이터가 없는 맥에서는 보이지 않아요.\n매일 같은 선택을 되풀이하기 싫다면 달력 화면 오른쪽 위의 '기본 표시 설정'을 써 보세요. 캘린더 일정을 기본으로 넣을지(모든 캘린더/선택한 캘린더만), 어떤 목표·행동을 기본으로 숨길지 한 번만 정하면 모든 날짜에 적용돼요. 날짜별 필터로 다르게 고른 날은 그 선택이 우선하고, '기본값으로 되돌리기'로 언제든 돌아갈 수 있어요.": { + "첫 페이지 타임테이블 카드의 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 보여줄 수 있어요 — 일정은 점선 테두리라 행동과 바로 구분돼요. 필터 버튼으로는 그날 기록된 행동 중 타임테이블에 올릴 것만 고르고, '이 날의 목표' 카드의 필터로는 보고 싶은 목표만 남겨요. 이 선택들은 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 담겨요.\n애플 건강의 수면·운동 시간도 실제 잔 시간·운동한 시간 그대로 옅은 블록으로 깔려요 — 기본은 수면 남보라·운동 주황이고, 색이 꼬리표와 헷갈리면 설정 → 건강 데이터 → '타임테이블 표시 색'에서 바꿀 수 있어요. 필터의 '건강 데이터' 토글로 켜고 끌 수 있고(기록 탭 타임테이블에도 함께 적용), 건강 데이터가 없는 맥에서는 보이지 않아요.\n매일 같은 선택을 되풀이하기 싫다면 달력 화면 오른쪽 위의 '기본 표시 설정'을 써 보세요. 캘린더 일정을 기본으로 넣을지(모든 캘린더/선택한 캘린더만), 어떤 목표·행동을 기본으로 숨길지 한 번만 정하면 모든 날짜에 적용돼요. 날짜별 필터로 다르게 고른 날은 그 선택이 우선하고, '기본값으로 되돌리기'로 언제든 돌아갈 수 있어요.": { "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Use the calendar button at the top right of the first page's timetable card to pick that day's calendar events to show alongside — events have dashed borders, so they're easy to tell from actions. The filter button picks which of that day's recorded actions go on the timetable, and the filter on the This Day's Goals card keeps only the goals you want. These choices are saved per date, and the timetable selection carries into diary exports as is.\nSleep and workout time from Apple Health also appear as light blocks, at the times you actually slept and worked out — indigo for sleep, orange for workouts. Toggle them with 'Health data' in the filter (applies to all dates); they don't appear on the Mac, which has no health data.\nTired of repeating the same choices every day? Try 'Default Display' at the top right of the calendar screen. Decide once whether to include calendar events by default (all calendars, or selected ones only) and which goals and actions to hide by default, and it applies to every date. Days where you picked differently keep their own choice, and 'Reset to Default' takes you back anytime." + "value": "Use the calendar button at the top right of the first page's timetable card to pick that day's calendar events to show alongside — events have dashed borders, so they're easy to tell from actions. The filter button picks which of that day's recorded actions go on the timetable, and the filter on the This Day's Goals card keeps only the goals you want. These choices are saved per date, and the timetable selection carries into diary exports as is.\nSleep and workout time from Apple Health also appear as light blocks, at the times you actually slept and worked out — indigo and orange by default, changeable in Settings → Health data → 'Timetable colors' if they clash with your tag colors. Toggle them with 'Health data' in the filter (this also applies to the History tab's timetable); they don't appear on the Mac, which has no health data.\nTired of repeating the same choices every day? Try 'Default Display' at the top right of the calendar screen. Decide once whether to include calendar events by default (all calendars, or selected ones only) and which goals and actions to hide by default, and it applies to every date. Days where you picked differently keep their own choice, and 'Reset to Default' takes you back anytime." } }, "ja": { "stringUnit": { "state": "translated", - "value": "最初のページのタイムテーブルカード右上のカレンダーボタンで、その日のカレンダー予定を選んで一緒に表示できます — 予定は破線の枠なのでアクションとすぐ区別できます。フィルターボタンではその日に記録したアクションのうちタイムテーブルに載せるものを選び、「この日の目標」カードのフィルターでは見たい目標だけを残します。これらの選択は日付ごとに保存され、タイムテーブルの選択は日記の書き出しにもそのまま反映されます。\nAppleヘルスケアの睡眠・運動時間も、実際に眠った・運動した時間どおり薄いブロックで敷かれます — 睡眠は藍色、運動はオレンジです。フィルターの「ヘルスデータ」トグルでオン・オフでき(すべての日付に共通)、ヘルスデータのないMacでは表示されません。\n毎日同じ選択を繰り返したくないなら、カレンダー画面右上の「デフォルト表示設定」を使ってみてください。カレンダー予定をデフォルトで入れるか(すべてのカレンダー/選んだカレンダーのみ)、どの目標・アクションをデフォルトで隠すかを一度決めれば、すべての日付に適用されます。日付別フィルターで別の選択をした日はその選択が優先され、「デフォルトに戻す」でいつでも戻れます。" + "value": "最初のページのタイムテーブルカード右上のカレンダーボタンで、その日のカレンダー予定を選んで一緒に表示できます — 予定は破線の枠なのでアクションとすぐ区別できます。フィルターボタンではその日に記録したアクションのうちタイムテーブルに載せるものを選び、「この日の目標」カードのフィルターでは見たい目標だけを残します。これらの選択は日付ごとに保存され、タイムテーブルの選択は日記の書き出しにもそのまま反映されます。\nAppleヘルスケアの睡眠・運動時間も、実際に眠った・運動した時間どおり薄いブロックで敷かれます — 基本の色は藍色とオレンジで、タグの色と紛らわしければ設定 → ヘルスデータ →「タイムテーブルの表示色」で変えられます。フィルターの「ヘルスデータ」トグルでオン・オフでき(記録タブのタイムテーブルにも適用)、ヘルスデータのないMacでは表示されません。\n毎日同じ選択を繰り返したくないなら、カレンダー画面右上の「デフォルト表示設定」を使ってみてください。カレンダー予定をデフォルトで入れるか(すべてのカレンダー/選んだカレンダーのみ)、どの目標・アクションをデフォルトで隠すかを一度決めれば、すべての日付に適用されます。日付別フィルターで別の選択をした日はその選択が優先され、「デフォルトに戻す」でいつでも戻れます。" } } } @@ -14986,6 +15018,22 @@ } } }, + "타임테이블 표시 색": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Timetable colors" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タイムテーブルの表示色" + } + } + } + }, "타임테이블 필터": { "localizations": { "en": { @@ -16287,18 +16335,18 @@ } } }, - "하루 정리에 그날의 건강 값을 담는 카드가 생기고, 타임테이블에는 수면·운동이 실제 시간 그대로 깔려요. 낮에 자는 날이 있다면 설정의 '수면 표시 구간'을 요일별로 맞춰 보세요.": { + "하루 정리에 그날의 건강 값을 담는 카드가 생기고, 일기·기록 탭 타임테이블에는 수면·운동이 실제 시간 그대로 깔려요(색은 설정에서 변경). 낮에 자는 날이 있다면 설정의 '수면 표시 구간'을 요일별로 맞춰 보세요.": { "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "The daily summary gains a card with that day's health values, and sleep and workouts now appear on the timetable at their actual times. If you sleep in the daytime some days, tune the 'Sleep display window' per weekday in Settings." + "value": "The daily summary gains a card with that day's health values, and the Diary and History timetables show sleep and workouts at their actual times (colors changeable in Settings). If you sleep in the daytime some days, tune the 'Sleep display window' per weekday in Settings." } }, "ja": { "stringUnit": { "state": "translated", - "value": "1日のまとめにその日のヘルス値をまとめるカードが加わり、タイムテーブルには睡眠・運動が実際の時間どおりに表示されます。昼間に眠る日があるなら、設定の「睡眠の表示時間帯」を曜日ごとに合わせてみてください。" + "value": "1日のまとめにその日のヘルス値をまとめるカードが加わり、日記・記録タブのタイムテーブルには睡眠・運動が実際の時間どおりに表示されます(色は設定で変更可)。昼間に眠る日があるなら、設定の「睡眠の表示時間帯」を曜日ごとに合わせてみてください。" } } } diff --git a/myApp/HaruDanim/IOS/Views/ExportImageView.swift b/myApp/HaruDanim/IOS/Views/ExportImageView.swift index c67929c..85ce08a 100644 --- a/myApp/HaruDanim/IOS/Views/ExportImageView.swift +++ b/myApp/HaruDanim/IOS/Views/ExportImageView.swift @@ -63,6 +63,33 @@ struct ExportSnapshot { return copy } + /// 타임테이블 섹션(하루·주간)에 건강(수면·운동) 블록을 날짜별로 끼워 넣는다 (1.5(4) — + /// 기록 탭 화면과 내보내기 일치용). 기록이 없는 시간대를 잘라낸 표시 범위(trimHours)는 + /// 블록이 걸치는 만큼 확장한다 — 새벽 수면이 잘린 그리드 밖으로 사라지지 않게 + func injectingHealthBlocks(forDayKeys dayKeys: [Date], math: DayMath) -> ExportSnapshot { + var copy = self + copy.sections = sections.map { section in + guard case .timetable(let title, let subtitle, var data) = section, + data.days.count == dayKeys.count else { return section } + var changed = false + for (index, key) in dayKeys.enumerated() { + let blocks = DiaryHealthTimetable.blocks(dayKey: key, math: math) + guard !blocks.isEmpty else { continue } + data.days[index].healthBlocks = blocks + changed = true + if let lo = blocks.map(\.startFrac).min() { + data.hourLo = min(data.hourLo, max(0, Int(lo.rounded(.down)))) + } + if let hi = blocks.map(\.endFrac).max() { + data.hourHi = max(data.hourHi, min(24, Int(hi.rounded(.up)))) + } + } + guard changed else { return section } + return .timetable(title: title, subtitle: subtitle, data) + } + return copy + } + /// 타임테이블 섹션만 다른 스냅숏의 것으로 교체. /// 일기의 날짜별 타임테이블 필터용 — 기록·통계 섹션은 전체(자신)를 유지한다. func replacingTimetableSections(with other: ExportSnapshot) -> ExportSnapshot { diff --git a/myApp/HaruDanim/IOS/Views/HealthSection.swift b/myApp/HaruDanim/IOS/Views/HealthSection.swift index 2421356..f341c83 100644 --- a/myApp/HaruDanim/IOS/Views/HealthSection.swift +++ b/myApp/HaruDanim/IOS/Views/HealthSection.swift @@ -362,8 +362,22 @@ struct HealthGuideSheet: View { enum DiaryHealthTimetable { /// 숨김 저장 키 (standard defaults — 기기 로컬, 모든 날짜 공통. "sleep"/"workout" 쉼표 연결) static let hiddenKey = "diary.timetableHealthHidden" - static let sleepColor = Color.indigo - static let workoutColor = Color.orange + + /// 블록 색 — 기본은 지표 고정색(수면 인디고·운동 주황)이지만, 꼬리표 색과 겹치면 + /// 설정 → 건강 데이터 → '타임테이블 표시 색'에서 바꿀 수 있다(모든 타임테이블 공통, 1.5(4)) + static var sleepColor: Color { customColor(LocalPrefsKeys.healthSleepColor) ?? .indigo } + static var workoutColor: Color { customColor(LocalPrefsKeys.healthWorkoutColor) ?? .orange } + + private static func customColor(_ key: String) -> Color? { + guard let hex = AppGroup.defaults.string(forKey: key), !hex.isEmpty else { return nil } + return Color(hex: hex) + } + + /// 수면 블록 병합 허용 간격 — 건강 앱 로우데이터는 수면 단계(코어·렘·깸)마다 쪼개져 있어 + /// 그대로 그리면 블록이 칸칸이 갈라진다(1.5(4) 실기기 실측). 이 간격 이하의 짧은 깸은 + /// 이어 붙여 한 덩어리로 그린다 — 사용자 규칙: "연속으로 잤으면 한 칸, 20분쯤 깼다 다시 + /// 잤으면 나뉘는 게 맞다" → 10분 기준. 값(수면 합계)은 실제 잔 시간 그대로라 무영향 + static let sleepDisplayMergeGap: TimeInterval = 10 * 60 static var hidden: Set { Set((UserDefaults.standard.string(forKey: hiddenKey) ?? "") @@ -385,9 +399,14 @@ enum DiaryHealthTimetable { func frac(_ date: Date) -> Double { date.timeIntervalSince(dayStart) / 3600 } var blocks: [ExportTimetableData.Block] = [] if !hiddenSet.contains("sleep") { - for interval in HealthIntervals.intervals(HealthIntervals.sleepKey, dayKey: dayKey) { + let merged = HealthDataStore.mergedIntervals( + HealthIntervals.intervals(HealthIntervals.sleepKey, dayKey: dayKey) + .map { ($0.start, $0.end) }, + tolerance: sleepDisplayMergeGap + ) + for interval in merged { blocks.append(ExportTimetableData.Block( - startFrac: frac(interval.start), endFrac: frac(interval.end), + startFrac: frac(interval.0), endFrac: frac(interval.1), color: sleepColor, symbol: HealthMetric.sleep.symbolName, name: HealthMetric.sleep.name )) @@ -600,6 +619,63 @@ struct SleepWindowSettingsView: View { } } +// MARK: - 타임테이블 표시 색 설정 (1.5(4) — 수면·운동 블록 색, 모든 타임테이블 공통) + +/// 수면·운동 블록의 표시 색. 기본은 지표 고정색(인디고·주황)이지만 꼬리표 색과 겹치면 +/// 여기서 바꾼다 — 일기·기록 탭 타임테이블과 내보내기에 모두 적용(단일 공급 지점이 +/// DiaryHealthTimetable.sleepColor/workoutColor라 자동 일치). +struct HealthColorSettingsView: View { + @AppStorage(LocalPrefsKeys.healthSleepColor, store: AppGroup.defaults) + private var sleepHex = "" + @AppStorage(LocalPrefsKeys.healthWorkoutColor, store: AppGroup.defaults) + private var workoutHex = "" + + private func binding(_ hex: Binding, fallback: Color) -> Binding { + Binding( + get: { hex.wrappedValue.isEmpty ? fallback : Color(hex: hex.wrappedValue) }, + set: { hex.wrappedValue = $0.hexString } + ) + } + + var body: some View { + List { + Section { + ColorPicker(selection: binding($sleepHex, fallback: .indigo), supportsOpacity: false) { + Label { + Text("수면") + } icon: { + Image(systemName: HealthMetric.sleep.symbolName) + .foregroundStyle(DiaryHealthTimetable.sleepColor) + } + } + ColorPicker(selection: binding($workoutHex, fallback: .orange), supportsOpacity: false) { + Label { + Text("운동") + } icon: { + Image(systemName: "figure.run") + .foregroundStyle(DiaryHealthTimetable.workoutColor) + } + } + } footer: { + Text("일기·기록 탭 타임테이블에 깔리는 수면·운동 블록의 색이에요. 꼬리표 색과 비슷해서 헷갈리면 바꿔 보세요 — 모든 타임테이블과 내보내기에 함께 적용돼요.") + } + if !sleepHex.isEmpty || !workoutHex.isEmpty { + Section { + Button("기본 색으로 되돌리기") { + sleepHex = "" + workoutHex = "" + } + .font(.callout) + } + } + } + .scrollContentBackground(.hidden) + .background(AppTheme.background) + .navigationTitle("타임테이블 표시 색") + .navigationBarTitleDisplayMode(.inline) + } +} + /// 수면 구간 스펙("시작분-끝분") 하나를 편집하는 시작·끝 휠 두 줄 private struct SleepWindowEditorRows: View { @Binding var spec: String diff --git a/myApp/HaruDanim/IOS/Views/HelpView.swift b/myApp/HaruDanim/IOS/Views/HelpView.swift index 6024a27..222429c 100644 --- a/myApp/HaruDanim/IOS/Views/HelpView.swift +++ b/myApp/HaruDanim/IOS/Views/HelpView.swift @@ -312,7 +312,7 @@ struct HelpView: View { HelpTopic( symbol: "heart.text.square.fill", title: "일기에서 건강 데이터 보기", - body: "일기의 하루 정리 첫 페이지에 '건강 데이터' 카드가 있어요 — 그날의 걸음·운동·수면 같은 값을 일기와 함께 봐요. 카드 오른쪽 위 버튼으로 보여줄 지표를 고르면 모든 날짜에 함께 적용돼요. 타임테이블에는 수면·운동이 실제 시간 그대로 옅은 블록으로 깔려요(수면 남보라·운동 주황) — 타임테이블 필터의 '건강 데이터' 토글로 켜고 끌 수 있어요.\n건강 값은 기기 안에만 있어서 카드의 값도 기기마다 다를 수 있어요 — 아이패드는 시스템의 건강 iCloud 동기화를 켠 경우에만 아이폰과 같은 값이 보이고, 맥에서는 카드와 블록이 보이지 않아요." + body: "일기의 하루 정리 첫 페이지에 '건강 데이터' 카드가 있어요 — 그날의 걸음·운동·수면 같은 값을 일기와 함께 봐요. 카드 오른쪽 위 버튼으로 보여줄 지표를 고르면 모든 날짜에 함께 적용돼요. 일기와 기록 탭의 타임테이블에는 수면·운동이 실제 시간 그대로 옅은 블록으로 깔려요 — 기본 색은 수면 남보라·운동 주황이고, 설정 → 건강 데이터 → '타임테이블 표시 색'에서 바꿀 수 있어요. 필터의 '건강 데이터' 토글로 켜고 끄면 두 타임테이블에 함께 적용돼요.\n건강 값은 기기 안에만 있어서 카드의 값도 기기마다 다를 수 있어요 — 아이패드는 시스템의 건강 iCloud 동기화를 켠 경우에만 아이폰과 같은 값이 보이고, 맥에서는 카드와 블록이 보이지 않아요." ), HelpTopic( symbol: "figure.run", @@ -352,7 +352,7 @@ struct HelpView: View { HelpTopic( symbol: "calendar.badge.plus", title: "타임테이블에 일정·필터", - body: "첫 페이지 타임테이블 카드의 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 보여줄 수 있어요 — 일정은 점선 테두리라 행동과 바로 구분돼요. 필터 버튼으로는 그날 기록된 행동 중 타임테이블에 올릴 것만 고르고, '이 날의 목표' 카드의 필터로는 보고 싶은 목표만 남겨요. 이 선택들은 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 담겨요.\n애플 건강의 수면·운동 시간도 실제 잔 시간·운동한 시간 그대로 옅은 블록으로 깔려요 — 수면은 남보라, 운동은 주황이에요. 필터의 '건강 데이터' 토글로 켜고 끌 수 있고(모든 날짜 공통), 건강 데이터가 없는 맥에서는 보이지 않아요.\n매일 같은 선택을 되풀이하기 싫다면 달력 화면 오른쪽 위의 '기본 표시 설정'을 써 보세요. 캘린더 일정을 기본으로 넣을지(모든 캘린더/선택한 캘린더만), 어떤 목표·행동을 기본으로 숨길지 한 번만 정하면 모든 날짜에 적용돼요. 날짜별 필터로 다르게 고른 날은 그 선택이 우선하고, '기본값으로 되돌리기'로 언제든 돌아갈 수 있어요." + body: "첫 페이지 타임테이블 카드의 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 보여줄 수 있어요 — 일정은 점선 테두리라 행동과 바로 구분돼요. 필터 버튼으로는 그날 기록된 행동 중 타임테이블에 올릴 것만 고르고, '이 날의 목표' 카드의 필터로는 보고 싶은 목표만 남겨요. 이 선택들은 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 담겨요.\n애플 건강의 수면·운동 시간도 실제 잔 시간·운동한 시간 그대로 옅은 블록으로 깔려요 — 기본은 수면 남보라·운동 주황이고, 색이 꼬리표와 헷갈리면 설정 → 건강 데이터 → '타임테이블 표시 색'에서 바꿀 수 있어요. 필터의 '건강 데이터' 토글로 켜고 끌 수 있고(기록 탭 타임테이블에도 함께 적용), 건강 데이터가 없는 맥에서는 보이지 않아요.\n매일 같은 선택을 되풀이하기 싫다면 달력 화면 오른쪽 위의 '기본 표시 설정'을 써 보세요. 캘린더 일정을 기본으로 넣을지(모든 캘린더/선택한 캘린더만), 어떤 목표·행동을 기본으로 숨길지 한 번만 정하면 모든 날짜에 적용돼요. 날짜별 필터로 다르게 고른 날은 그 선택이 우선하고, '기본값으로 되돌리기'로 언제든 돌아갈 수 있어요." ), HelpTopic( symbol: "applepencil", diff --git a/myApp/HaruDanim/IOS/Views/HistoryView.swift b/myApp/HaruDanim/IOS/Views/HistoryView.swift index 43387b6..3b69469 100644 --- a/myApp/HaruDanim/IOS/Views/HistoryView.swift +++ b/myApp/HaruDanim/IOS/Views/HistoryView.swift @@ -154,11 +154,13 @@ struct HistoryView: View { CountEntryEditorView(entry: entry) } .sheet(isPresented: $showingFilter) { + // 건강 섹션(수면·운동 블록 토글)은 일기 필터와 같은 값 공유 — 타임테이블 공통 (1.5(4)) RecordFilterSheet( title: "기록 필터", tags: tags, actions: allActions, - excludedActionIDs: $excludedActionIDs + excludedActionIDs: $excludedActionIDs, + showsHealthSection: true ) } .onAppear { @@ -415,16 +417,21 @@ private struct HistoryRecordsView: View { } .sheet(isPresented: $showingExport) { // 지금 보고 있는 날짜/보기 모드/필터 그대로 스냅숏을 만들어 이미지로 내보낸다 - // (스냅숏 집계 범위는 하루 또는 이 주간 — 조회 범위와 일치) + // (스냅숏 집계 범위는 하루 또는 이 주간 — 조회 범위와 일치). + // 건강 블록도 화면과 동일하게 주입 — 하루/주간 각각의 날짜 키로 (1.5(4)) + let weekly = mode == .timetable && timetableWeekly + let healthDayKeys = weekly + ? math.dayKeys(in: math.weekRange(containing: math.dayRange(forKey: dayKey).lowerBound)) + : [dayKey] ExportImageSheet(snapshot: ExportBuilder.history( dayKey: dayKey, - weeklyTimetable: mode == .timetable && timetableWeekly, + weeklyTimetable: weekly, sessions: sessions, entries: entries, orderedActions: orderedActions, excludedActionIDs: excludedActionIDs, math: math - )) + ).injectingHealthBlocks(forDayKeys: healthDayKeys, math: math)) } } @@ -558,6 +565,10 @@ struct TimetableView: View { let onTapSession: (TimeSession) -> Void let onTapEntry: (CountEntry) -> Void + /// 수면·운동 블록 숨김 — 일기 타임테이블 필터와 같은 값(전 타임테이블 공통, 1.5(4)). + /// @AppStorage로 읽어 필터 시트 토글이 즉시 재렌더된다 + @AppStorage(DiaryHealthTimetable.hiddenKey) private var healthHiddenRaw = "" + private var hourHeight: CGFloat { weekly ? 26 : 48 } var body: some View { @@ -627,9 +638,17 @@ struct TimetableView: View { let range = math.dayRange(forKey: key) let daySegments = segments(range) let dayCounts = counts(range) + // 수면·운동 실구간 — 일기 타임테이블과 같은 단일 공급 지점(색·병합·숨김 규칙 동일, 1.5(4)) + let healthBlocks = DiaryHealthTimetable.blocks( + dayKey: key, math: math, + hidden: Set(healthHiddenRaw.split(separator: ",").map(String.init)) + ) return GeometryReader { geo in ZStack(alignment: .topLeading) { gridLines(width: geo.size.width) + ForEach(healthBlocks) { item in + healthBlock(item, width: geo.size.width) + } ForEach(daySegments) { item in block(item, in: range, width: geo.size.width) } @@ -640,6 +659,36 @@ struct TimetableView: View { } .frame(height: hourHeight * 24) .background(AppTheme.surface, in: RoundedRectangle(cornerRadius: 8, style: .continuous)) + .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous)) + } + + /// 건강(수면·운동) 블록 — 내보내기(healthBlockView)와 같은 반투명+실선 문법, 표시 전용(탭 없음) + private func healthBlock(_ item: ExportTimetableData.Block, width: CGFloat) -> some View { + let y = CGFloat(item.startFrac) * hourHeight + let height = max(CGFloat(item.endFrac - item.startFrac) * hourHeight, 5) + return RoundedRectangle(cornerRadius: 4, style: .continuous) + .fill(item.color.opacity(0.16)) + .overlay( + RoundedRectangle(cornerRadius: 4, style: .continuous) + .strokeBorder(item.color.opacity(0.55), lineWidth: 1) + ) + .overlay(alignment: .topLeading) { + if !weekly && height >= 18 { + Label(item.name, systemImage: item.symbol) + .font(.caption2.weight(.semibold)) + .foregroundStyle(item.color) + .lineLimit(1) + .padding(4) + } else if height >= 12 { + Image(safeSymbol: item.symbol) + .font(.system(size: 7, weight: .bold)) + .foregroundStyle(item.color) + .padding(2) + } + } + .frame(width: max(width - 4, 8), height: height) + .offset(x: 2, y: y) + .accessibilityLabel(Text(verbatim: item.name)) } private func gridLines(width: CGFloat) -> some View { diff --git a/myApp/HaruDanim/IOS/Views/RecordFilterSheet.swift b/myApp/HaruDanim/IOS/Views/RecordFilterSheet.swift index ac74ef6..63d8060 100644 --- a/myApp/HaruDanim/IOS/Views/RecordFilterSheet.swift +++ b/myApp/HaruDanim/IOS/Views/RecordFilterSheet.swift @@ -112,7 +112,7 @@ struct RecordFilterSheet: View { Text("건강 데이터") } footer: { if HealthDataStore.isAvailable { - Text("애플 건강의 수면·운동 시간을 실제 구간 그대로 타임테이블에 보여줘요. 이 설정은 모든 날짜에 함께 적용돼요.") + Text("애플 건강의 수면·운동 시간을 실제 구간 그대로 타임테이블에 보여줘요. 이 설정은 일기·기록 탭의 모든 타임테이블에 함께 적용되고, 블록 색은 설정 → 건강 데이터에서 바꿀 수 있어요.") } } } diff --git a/myApp/HaruDanim/IOS/Views/SettingsView.swift b/myApp/HaruDanim/IOS/Views/SettingsView.swift index 07d19c9..22cfaf6 100644 --- a/myApp/HaruDanim/IOS/Views/SettingsView.swift +++ b/myApp/HaruDanim/IOS/Views/SettingsView.swift @@ -212,6 +212,19 @@ struct SettingsView: View { .lineLimit(1) } } + // 1.5(4): 타임테이블 수면·운동 블록 색 (모든 타임테이블 공통) + NavigationLink { + HealthColorSettingsView() + } label: { + HStack { + Text("타임테이블 표시 색") + Spacer() + Circle().fill(DiaryHealthTimetable.sleepColor) + .frame(width: 14, height: 14) + Circle().fill(DiaryHealthTimetable.workoutColor) + .frame(width: 14, height: 14) + } + } } header: { Text("건강 데이터") } footer: { diff --git a/myApp/HaruDanim/IOS/Views/WhatsNewView.swift b/myApp/HaruDanim/IOS/Views/WhatsNewView.swift index 759dd89..c8d3474 100644 --- a/myApp/HaruDanim/IOS/Views/WhatsNewView.swift +++ b/myApp/HaruDanim/IOS/Views/WhatsNewView.swift @@ -86,7 +86,7 @@ struct WhatsNewView: View { symbol: "heart.text.square.fill", tint: .pink, title: String(localized: "일기에서 건강 데이터 보기"), - body: String(localized: "하루 정리에 그날의 건강 값을 담는 카드가 생기고, 타임테이블에는 수면·운동이 실제 시간 그대로 깔려요. 낮에 자는 날이 있다면 설정의 '수면 표시 구간'을 요일별로 맞춰 보세요.") + body: String(localized: "하루 정리에 그날의 건강 값을 담는 카드가 생기고, 일기·기록 탭 타임테이블에는 수면·운동이 실제 시간 그대로 깔려요(색은 설정에서 변경). 낮에 자는 날이 있다면 설정의 '수면 표시 구간'을 요일별로 맞춰 보세요.") ) feature( symbol: "questionmark.circle.fill", diff --git a/myApp/HaruDanim/Marketing/AppStore/whats-new-1.5.txt b/myApp/HaruDanim/Marketing/AppStore/whats-new-1.5.txt index 5a1fc45..603d05a 100644 --- a/myApp/HaruDanim/Marketing/AppStore/whats-new-1.5.txt +++ b/myApp/HaruDanim/Marketing/AppStore/whats-new-1.5.txt @@ -11,7 +11,7 @@ ● 일기가 더 넓어졌어요 (프리미엄) • 일기 탭이 아이폰에도 생겼어요 — 하루 정리를 확인하고, '그리기'를 켜면 손가락으로 노트에 쓸 수 있어요 • 하루 정리에 그날의 건강 값을 담는 '건강 데이터' 카드가 생겼어요 -• 타임테이블에 수면·운동이 실제 시간 그대로 깔려요 — 하루가 정말 한눈에 정리돼요 +• 일기·기록 탭 타임테이블에 수면·운동이 실제 시간 그대로 깔려요 — 색도 취향대로 바꿀 수 있어요 ● 애플워치 '행동 기록' 컴플리케이션 • 행동의 누적 기록(하루·이번 주·이번 달·지난 7일·지난 30일)을 워치 페이스에서 바로 봐요 @@ -32,7 +32,7 @@ This update brings Haru Danim closer to Apple Health — and your Apple Watch. A ● A bigger Diary (Premium) • The Diary tab is now on iPhone — review your day, and turn on Draw to write on note pages with your finger • The daily summary gains a Health Data card with that day's values -• Sleep and workouts now appear on the timetable at their actual times — your whole day, truly at a glance +• Sleep and workouts now appear on the Diary and History timetables at their actual times — with colors you can customize ● Action Log complication for Apple Watch • See an action's running total (day, this week, this month, last 7/30 days) right on your watch face @@ -53,7 +53,7 @@ This update brings Haru Danim closer to Apple Health — and your Apple Watch. A ● 日記がもっと広く(プレミアム) • 日記タブがiPhoneにも登場 — 1日のまとめを確認し、「描く」をオンにすれば指でノートに書けます • 1日のまとめに、その日のヘルス値をまとめる「ヘルスデータ」カードが加わりました -• タイムテーブルに睡眠・運動が実際の時間どおりに表示されます — 1日が本当にひと目でわかります +• 日記・記録タブのタイムテーブルに睡眠・運動が実際の時間どおりに表示されます — 色も好みに変えられます ● Apple Watch「アクションの記録」コンプリケーション • アクションの累計(1日・今週・今月・過去7日・過去30日)をウォッチフェイスで直接確認 diff --git a/myApp/HaruDanim/Shared/LocalPrefs.swift b/myApp/HaruDanim/Shared/LocalPrefs.swift index c0d36f7..86f12d5 100644 --- a/myApp/HaruDanim/Shared/LocalPrefs.swift +++ b/myApp/HaruDanim/Shared/LocalPrefs.swift @@ -46,6 +46,10 @@ nonisolated enum LocalPrefsKeys { /// 모음 탭 건강 타일에 표시할 지표 — HealthMetric raw 쉼표 연결(순서 포함, 1.5). /// 기본: 걸음·운동·수면 static let healthMetrics = "local.healthMetrics" + /// 타임테이블 수면 블록 색 — "#RRGGBB", 빈 값=기본(인디고). 모든 타임테이블 공통 (1.5(4)) + static let healthSleepColor = "local.healthSleepColor" + /// 타임테이블 운동 블록 색 — "#RRGGBB", 빈 값=기본(주황). 모든 타임테이블 공통 (1.5(4)) + static let healthWorkoutColor = "local.healthWorkoutColor" /// 수면 표시 구간(타일·일기 건강 카드 공용) — "시작분-끝분" 스펙, 빈 값=기본(1260-540) (1.5) static let sleepWindowGlobal = "local.sleepWindowGlobal" /// 요일별 수면 구간 사용 여부 — Bool (1.5)