diff --git a/myApp/HaruDanim/CLAUDE.md b/myApp/HaruDanim/CLAUDE.md index ab8ab34..8e6ec9d 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 (빌드 9) — 소킹 사용자 요구·실측 수정+성능 최적화 반영(2026-08-26), TestFlight 재업로드 대기. 사용자 예고: 기능 수정은 이것으로 종료(이후는 버그·오류 수정만)**. ✅**동기화 정지 이슈 해결 확인(2026-08-22 밤, 사용자 실기기)**: 일기에 도형·텍스트 상자(DiaryPageItem)가 있으면 그 기기의 CloudKit 동기화 전체가 정지하던 문제(내보내기 대기열 직렬 — 거부된 아이템 뒤로 전부 막힘) — 원인은 **1.3의 DiaryPageItem 스타일 필드 10종이 프로덕션 스키마에 없었던 것**(1.0 출시 때가 마지막 배포였고, 1.3 이후 개발 빌드가 iCloud 내보내기를 한 적이 없어 개발 스키마에도 미생성이었음). 해결 절차 실행: 시뮬 519869F7(사용자 iCloud 로그인·재인증)에서 일기 요소 시드 포함 전체 내보내기로 개발 스키마 필드 생성(success·error nil) → 사용자가 콘솔 재배포 → **도형·텍스트 상자 있는 상태의 동기화 왕복 정상 확인(사용자)**. 교훈: **새 모델 필드는 배포 전 '개발 빌드로 그 타입 레코드를 실제 내보냈는지'까지 확인할 것** — 콘솔 diff에 타입 이름이 떠도 필드가 전부 실렸는지 봐야 한다. 업로드 검증 실측 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`) ⑮**버블 찌그러짐 실측 수정(빌드 6, 2026-08-23)**: 일기 버블 추가로 8개가 되자 이웃 간격(~9.6pt)이 유리 합체 판정 거리(18pt) 안으로 들어와 정지 상태에서도 원들이 서로를 향해 늘어남 — `glassBlendSpacing` 18→6(펼침 상태 정원 분리, FAB 모핑은 겹침 구간이라 유지. 마케팅 06-bubble 3언어 재촬영) ⑯**건강 다짐 표시 색(빌드 7, 2026-08-23 — 사용자 요구)**: 건강 다짐이 고정 분홍이던 것을 편집기 건강 섹션에서 색 선택(프리셋 12+피커, 기본 분홍=빈 값[시스템 분홍의 다크 적응 유지]) — **Quest 신규 필드 `healthColorHex`**(기본 "", targetColor 단일 지점 분기라 목표 탭·모음 카드·일기·위젯·워치 자동 반영, 행동·꼬리표 다짐은 저장 시 "" 정규화, 타임테이블 블록 색과 별개). ⚠️**CloudKit 스키마 추가 — 배포 절차(개발 내보내기→콘솔 Deploy)가 빌드 7 설치보다 선행 필수**(DiaryPageItem 전례 — 미배포 상태로 다짐 수정 시 그 기기 동기화 정지). → 배포·설치 완료(사용자). ⑰**위젯·워치 색 미반영 실측 수정(빌드 8)**: 위젯 QuestCellSnapshot(WidgetSupport)과 워치 스냅숏(WatchSyncManager)이 targetColor를 안 타고 자체 폴백을 쓰고 있었음 — 위젯은 분홍 #E0558C 하드코딩, **워치는 아예 초록 폴백(1.5 출시분부터 건강 다짐이 일반색으로 보이던 잠복 표시 버그)** → 두 곳 다 healthColorHex 우선·기본 #E0558C로 통일(④ 그리드에서 걸음=파랑·나머지=분홍 실측). 값 라벨은 두 표면 모두 원래 건강 단위 정상. ⑱**성능 최적화(빌드 9, 2026-08-26 — 소킹 중 '1.4 대비 버벅임 증가' 보고, 동작·수치 완전 불변 원칙)**: 원인 진단 결과 1.5 건강 캐시가 값 1개 읽을 때마다 전체 딕셔너리(지표×400일)를 브리징 — 건강 다짐 행 하나 렌더에 ~38회+, 워치 스냅숏에 다짐당 ~76회가 메인 스레드에서 반복. 적용 7건(전부 등가 최적화): ⓐHealthCache·HealthIntervals **인메모리 캐시**(세대 카운터 `health.cache.gen`/`health.intervals.gen`으로 프로세스 간 무효화 — 쓰기는 메인 앱 단일 작성자, workoutKeys의 load() 이중 호출도 제거) ⓑ**DayMath.calendar 저장 프로퍼티화**(접근마다 Calendar 사본 생성하던 전역 곱셈 비용 제거) ⓒ**수면 백필 포인터 스위프**(400일×전체 수면 샘플 O(n×m) 전수 클리핑 → 정렬+lo/hi 스위프, 무작위 퍼즈 3,000회 등가 증명 — 하루 첫 실행 정지 해소) ⓓrefreshRecent 캐시 기록 배칭(일수×2회→총 2회) ⓔ**QuestProgress.spanValue/spanRawValue 버킷화**(하루 단위 다짐의 주/월 게이지가 날마다 전체 기록 재순회하던 O(일수×기록수) → perDayGaugeValues 1회 순회. ⚠️연속 계산 perDayValues와 달리 **미래 기록을 now로 자르지 않는 게이지 스펙 보존** — spanRawValue는 예전 Aggregator-only 동작 보존 위해 perDayRecordValues 직결) ⓕ위젯 라이브 타임라인 makeEntry에 refresh 파라미터(미래 엔트리 6개가 각각 새 ModelContainer를 열던 것 → 타임라인당 1회, ②③④·잠금 4곳) ⓖ일기 건강 카드 칩 value() 이중 호출 제거. **의도적으로 제외**: 재조회 스로틀(값 신선도가 변해 동작 변화), 워치 스냅숏 백그라운드화(SwiftData 메인 컨텍스트 위험), 모음 탭 셀 오늘 값 버킷화(구조 변경 — 체감 보고 재논의). 검증: A/B 등가(옛/새 빌드를 각각 새 컨테이너+동일 시드로 캡처 — streak·건강·인텐트 덤프 3종 값 동일[정렬 후 diff 0], 목표·모음·위젯④·일기 4화면 **픽셀 동일**[상태바 시계·시뮬 iCloud 배너 제외]), 자가 검증 62/20/27/10 ALL PASS(26.5+**18.5 심 62건도 ALL PASS**), 벌크 1만 건 3탭 렌더 정상, 퍼즈 3,000회, 3종 빌드(Store·워치 포함) 그린, 신규 현지화 문자열 0(카탈로그 무변경). whats-new-1.5 3언어에 성능 개선 줄 추가. 검증: 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 (빌드 9) — 소킹 사용자 요구·실측 수정+성능 최적화 반영(2026-08-26), TestFlight 재업로드 대기. 사용자 예고: 기능 수정은 이것으로 종료(이후는 버그·오류 수정만)**. ✅**동기화 정지 이슈 해결 확인(2026-08-22 밤, 사용자 실기기)**: 일기에 도형·텍스트 상자(DiaryPageItem)가 있으면 그 기기의 CloudKit 동기화 전체가 정지하던 문제(내보내기 대기열 직렬 — 거부된 아이템 뒤로 전부 막힘) — 원인은 **1.3의 DiaryPageItem 스타일 필드 10종이 프로덕션 스키마에 없었던 것**(1.0 출시 때가 마지막 배포였고, 1.3 이후 개발 빌드가 iCloud 내보내기를 한 적이 없어 개발 스키마에도 미생성이었음). 해결 절차 실행: 시뮬 519869F7(사용자 iCloud 로그인·재인증)에서 일기 요소 시드 포함 전체 내보내기로 개발 스키마 필드 생성(success·error nil) → 사용자가 콘솔 재배포 → **도형·텍스트 상자 있는 상태의 동기화 왕복 정상 확인(사용자)**. 교훈: **새 모델 필드는 배포 전 '개발 빌드로 그 타입 레코드를 실제 내보냈는지'까지 확인할 것** — 콘솔 diff에 타입 이름이 떠도 필드가 전부 실렸는지 봐야 한다. 업로드 검증 실측 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`) ⑮**버블 찌그러짐 실측 수정(빌드 6, 2026-08-23)**: 일기 버블 추가로 8개가 되자 이웃 간격(~9.6pt)이 유리 합체 판정 거리(18pt) 안으로 들어와 정지 상태에서도 원들이 서로를 향해 늘어남 — `glassBlendSpacing` 18→6(펼침 상태 정원 분리, FAB 모핑은 겹침 구간이라 유지. 마케팅 06-bubble 3언어 재촬영) ⑯**건강 다짐 표시 색(빌드 7, 2026-08-23 — 사용자 요구)**: 건강 다짐이 고정 분홍이던 것을 편집기 건강 섹션에서 색 선택(프리셋 12+피커, 기본 분홍=빈 값[시스템 분홍의 다크 적응 유지]) — **Quest 신규 필드 `healthColorHex`**(기본 "", targetColor 단일 지점 분기라 목표 탭·모음 카드·일기·위젯·워치 자동 반영, 행동·꼬리표 다짐은 저장 시 "" 정규화, 타임테이블 블록 색과 별개). ⚠️**CloudKit 스키마 추가 — 배포 절차(개발 내보내기→콘솔 Deploy)가 빌드 7 설치보다 선행 필수**(DiaryPageItem 전례 — 미배포 상태로 다짐 수정 시 그 기기 동기화 정지). → 배포·설치 완료(사용자). ⑰**위젯·워치 색 미반영 실측 수정(빌드 8)**: 위젯 QuestCellSnapshot(WidgetSupport)과 워치 스냅숏(WatchSyncManager)이 targetColor를 안 타고 자체 폴백을 쓰고 있었음 — 위젯은 분홍 #E0558C 하드코딩, **워치는 아예 초록 폴백(1.5 출시분부터 건강 다짐이 일반색으로 보이던 잠복 표시 버그)** → 두 곳 다 healthColorHex 우선·기본 #E0558C로 통일(④ 그리드에서 걸음=파랑·나머지=분홍 실측). 값 라벨은 두 표면 모두 원래 건강 단위 정상. ⑱**성능 최적화(빌드 9, 2026-08-26 — 소킹 중 '1.4 대비 버벅임 증가' 보고, 동작·수치 완전 불변 원칙)**: 원인 진단 결과 1.5 건강 캐시가 값 1개 읽을 때마다 전체 딕셔너리(지표×400일)를 브리징 — 건강 다짐 행 하나 렌더에 ~38회+, 워치 스냅숏에 다짐당 ~76회가 메인 스레드에서 반복. 적용 7건(전부 등가 최적화): ⓐHealthCache·HealthIntervals **인메모리 캐시**(세대 카운터 `health.cache.gen`/`health.intervals.gen`으로 프로세스 간 무효화 — 쓰기는 메인 앱 단일 작성자, workoutKeys의 load() 이중 호출도 제거) ⓑ**DayMath.calendar 저장 프로퍼티화**(접근마다 Calendar 사본 생성하던 전역 곱셈 비용 제거) ⓒ**수면 백필 포인터 스위프**(400일×전체 수면 샘플 O(n×m) 전수 클리핑 → 정렬+lo/hi 스위프, 무작위 퍼즈 3,000회 등가 증명 — 하루 첫 실행 정지 해소) ⓓrefreshRecent 캐시 기록 배칭(일수×2회→총 2회) ⓔ**QuestProgress.spanValue/spanRawValue 버킷화**(하루 단위 다짐의 주/월 게이지가 날마다 전체 기록 재순회하던 O(일수×기록수) → perDayGaugeValues 1회 순회. ⚠️연속 계산 perDayValues와 달리 **미래 기록을 now로 자르지 않는 게이지 스펙 보존** — spanRawValue는 예전 Aggregator-only 동작 보존 위해 perDayRecordValues 직결) ⓕ위젯 라이브 타임라인 makeEntry에 refresh 파라미터(미래 엔트리 6개가 각각 새 ModelContainer를 열던 것 → 타임라인당 1회, ②③④·잠금 4곳) ⓖ일기 건강 카드 칩 value() 이중 호출 제거. **의도적으로 제외**: 재조회 스로틀(값 신선도가 변해 동작 변화), 워치 스냅숏 백그라운드화(SwiftData 메인 컨텍스트 위험), 모음 탭 셀 오늘 값 버킷화(구조 변경 — 체감 보고 재논의). 검증: A/B 등가(옛/새 빌드를 각각 새 컨테이너+동일 시드로 캡처 — streak·건강·인텐트 덤프 3종 값 동일[정렬 후 diff 0], 목표·모음·위젯④·일기 4화면 **픽셀 동일**[상태바 시계·시뮬 iCloud 배너 제외]), 자가 검증 62/20/27/10 ALL PASS(26.5+**18.5 심 62건도 ALL PASS**), 벌크 1만 건 3탭 렌더 정상, 퍼즈 3,000회, 3종 빌드(Store·워치 포함) 그린, 신규 현지화 문자열 0(카탈로그 무변경). whats-new-1.5 3언어에 성능 개선 줄 추가. ⑲**워치 미니 행동 화면 재구성(빌드 9 동일, 2026-08-26 — 사용자 울트라 실측: 하단 실행 버튼이 살짝 잘림)**: 하단 버튼 줄 제거, **아이콘=실행 버튼**(우하단 +/▶/■ 배지, 측정 중=노란 테두리+노란 ■ 배지+노란 타이머, 별도 '측정 중' 줄은 중복이라 제거) + 상태별 한 줄 힌트('아이콘을 누르면 시작/종료/1회 추가' — lineLimit 1+축소 0.75로 ja 40mm 한 줄 유지). 작은 워치(높이<220pt)는 컴팩트 메트릭(아이콘 54·간격 6)으로 **40mm에서도 힌트까지 스크롤 없이 한 화면**. a11y 라벨은 기존 버튼 문구 유지, 실행 경로는 동일한 store.run. 도움말 '행동 기록 컴플리케이션' 문구 갱신(3언어), 검증: 49mm(울트라)·46mm·40mm(11.5) × 3상태 + ja 렌더 스크린샷 전부 잘림 0, 워치·iOS·Store 빌드 그린, 전 카탈로그 0/0. 신규 검증 인자 `-watchSeedSnapshot`(페어링 없는 합성 스냅숏)·`-watchOpenActionIndex N`(§14). 검증: 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 체크리스트 | --- @@ -312,7 +312,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f - iPhone→워치: `updateApplicationContext`(기본) + `transferCurrentComplicationUserInfo`(측정 상태 변경 또는 30분 경과 시만 — 일일 예산 절약) - 워치→iPhone: `sendMessage`(응답=최신 스냅숏), 실패/미활성 시 `transferUserInfo` 큐 폴백 (5분 지난 큐 명령은 무시) - 스냅숏은 워치 App Group defaults에 캐시(오래된 스냅숏 역행 방지: generatedAt 비교) → 컴플리케이션이 읽음 -- **컴플리케이션** 4종 (15분 .after 타임라인): **행동 기록(1.5)** — 옵트인 행동의 설정 기간 누적(`local.watchActions`, 측정 중 노란 타이머+유령 타이머 안전장치 재사용, 탭=widgetURL 딥링크→워치 앱 미니 행동 화면 push[NavigationPath — 뒤로가기로 메인 복귀], 스냅숏 optional 기간 필드로 구버전 캐시 호환) / 현재 현황(대표 행동+타이머+N) / 목표 달성률(기간 선택, 게이지) / 다짐 달성률(rectangular는 3기간 모두, **atMost는 게이지 대신 한도 안/초과 상태 표현**) +- **컴플리케이션** 4종 (15분 .after 타임라인): **행동 기록(1.5)** — 옵트인 행동의 설정 기간 누적(`local.watchActions`, 측정 중 노란 타이머+유령 타이머 안전장치 재사용, 탭=widgetURL 딥링크→워치 앱 미니 행동 화면 push[NavigationPath — 뒤로가기로 메인 복귀. 1.5(9)부터 하단 버튼 없이 아이콘=실행 버튼(+/▶/■ 배지·상태 힌트 줄, 40mm도 스크롤 없이 한 화면 — 작은 화면은 컴팩트 메트릭)], 스냅숏 optional 기간 필드로 구버전 캐시 호환) / 현재 현황(대표 행동+타이머+N) / 목표 달성률(기간 선택, 게이지) / 다짐 달성률(rectangular는 3기간 모두, **atMost는 게이지 대신 한도 안/초과 상태 표현**) - **추천 목록(recommendations) = 선택지 전부**: 워치는 컴플리케이션 파라미터 편집 UI가 없어 페이스 편집기의 목록이 recommendations()가 돌려준 조합으로 한정된다 — 스냅숏에 담긴 **목표 전부 × 3기간, 다짐 전부 × 3기간**을 상한 없이 나열할 것(예전 prefix(2) 캡이 "3번째 목표부터 페이스에 추가 불가" 버그였음). 워치 앱이 스냅숏을 적용할 때 `invalidateConfigurationRecommendations()`를 호출해 목표·다짐 변경이 목록에 자동 반영된다 - **스냅숏의 목표 = 진행 중 ∩ '애플워치에서 보기' 켠 것**(`makeSnapshot`의 단일 필터 지점, `LocalPrefs.showsOnWatch`) — 목표·다짐이 많아지면 목록이 폭증하므로 옵트인(기본 전부 꺼짐 → 컴플리케이션은 '목표 없음' 빈 상태로 시작 — **rectangular 빈 상태는 "목표 편집에서 '애플워치에서 보기'를 켜면 나타나요" 해결 안내 포함**, circular/inline은 공간상 짧은 문구 유지, 2026-07-23). 완료 목표는 진행 중 조건으로 자동 제외. 행동·측정 중 상태(워치 앱 화면, 현재 현황)는 필터와 무관. 페이스에 이미 올린 미체크·완료 목표 컴플리케이션은 스냅숏 첫 목표로 폴백(없으면 빈 상태) - **유령 타이머 안전장치**(현재 현황): '측정 중' 스냅숏이 `generatedAt` 기준 2시간을 넘기면 무한 타이머 대신 "새로고침 필요" 표시로 강등(`StatusProvider.runningTrustInterval`) — 종료 푸시가 유실돼도 유령 타이머가 몇 시간씩 남지 않음. 워치 앱을 열면 즉시 복구, 진짜 장시간 측정도 다음 스냅숏에 타이머 복귀(데이터 무영향) @@ -358,7 +358,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f | 설정/기타 | `-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, 착지·뒤로가기 검증) | +| 워치 | `-complicationPreview` `-complicationScroll` `-complicationScrollTo ` `-autoRunFirstAction` `-autoStopRunning`(측정 중 행 탭과 동일 경로로 대표 측정 종료 — 페어링 심 E2E용) `-watchOpenActionDetail`(1.5 — 스냅숏 수신 후 첫 옵트인 행동의 미니 행동 화면을 딥링크와 같은 경로로 push, 착지·뒤로가기 검증. `-watchOpenActionIndex N`을 함께 주면 배치순 N번째[0부터] 행동을 염 — 상태별 QA) `-watchSeedSnapshot`(1.5(9) — 페어링된 iPhone 없이 합성 스냅숏[횟수형 옵트인·측정 중 시간형·대기 시간형 3종] 적용, WCSession 미활성 — 미니 화면·목록 렌더 QA용) | --- diff --git a/myApp/HaruDanim/Docs/plan-1.5.md b/myApp/HaruDanim/Docs/plan-1.5.md index fbe190e..094bdab 100644 --- a/myApp/HaruDanim/Docs/plan-1.5.md +++ b/myApp/HaruDanim/Docs/plan-1.5.md @@ -199,3 +199,10 @@ - **적용 7건**: ⓐ건강 캐시 2종 인메모리화(세대 카운터로 프로세스 간 무효화, workoutKeys 이중 load 제거) ⓑDayMath.calendar 저장 프로퍼티화 ⓒ수면 백필 정렬+포인터 스위프 ⓓrefreshRecent 기록 배칭 ⓔspanValue/spanRawValue 하루 버킷화(**게이지의 '미래 기록 즉시 반영' 스펙 보존** — streak의 perDayValues와 별도 헬퍼 perDayGaugeValues/perDayRecordValues) ⓕ위젯 4프로바이더 makeEntry refresh 파라미터(타임라인당 컨테이너 1회) ⓖ일기 건강 카드 칩 value() 1회화. - **의도적 제외**: 건강 재조회 스로틀(신선도 변화=동작 변화), 워치 스냅숏 백그라운드화(SwiftData 메인 컨텍스트 위험), 모음 탭 셀 오늘 값 버킷화(구조 변경 — 소킹 체감 보고 후 재논의). - **검증(엄밀 A/B 포함)**: ①옛/새 빌드를 각각 **새 컨테이너+동일 시드**(seedDemo+건강 캐시+건강 다짐+주기 몫)로 캡처 — streak·건강 다짐·인텐트 스모크 덤프 3종 **값 완전 동일**(순서 무관 정렬 diff 0 — fetch 순서 비결정성만 존재), 목표 탭·모음 탭·위젯 ④·일기 4화면 **픽셀 단위 동일**(자작 pixdiff — 상태바 시계·시뮬 iCloud 배너만 제외) ②자가 검증 62/20/27/10 ALL PASS(26.5 심+**18.5 심 62건 별도 ALL PASS**) ③수면 스위프는 무작위 퍼즈 **3,000회 등가 증명**(창 40/400/7개×길이 1분~24h×DST식 겹침 창×구간 0~2,000개×경계 정확 접촉·0길이·전체 덮개) ④벌크 1만 건 시드에서 목표·통계·모음 3탭 렌더 정상(수치 실측 일치) ⑤3종 빌드(Debug·Store·워치) 그린 ⑥신규 현지화 문자열 0(카탈로그 무변경). 스토어: whats-new-1.5 3언어에 성능 개선 1줄 추가. + +### §12 후속 5 — 워치 미니 행동 화면 재구성 (2026-08-26, 빌드 9 동일) + +- **발단(사용자 울트라 실측)**: '행동 기록' 컴플리케이션 착지 화면(미니 행동 화면)의 하단 실행 버튼이 가장 큰 화면(울트라 49mm)에서도 아래가 살짝 잘림 — 동작 지장은 없지만 디테일 아쉬움. 사용자 제안(하단 버튼 제거+아이콘=버튼) 채택. +- **새 구성**: [뒤로가기(시스템)] / **아이콘 타일=실행 버튼**(64pt, 우하단 배지 +(횟수)·▶(대기)·■(측정 중), 측정 중=노란 테두리+노란 배지 — 앱 전체 '측정 중=노랑' 문법) / 이름 / 기간 라벨+큰 값(측정 중 노란 타이머) / **상태별 한 줄 힌트**("아이콘을 누르면 시작/종료/1회 추가"). 별도 '측정 중' 줄은 사중 중복이라 제거. 실행 경로는 기존과 동일한 store.run(햅틱 포함) — 트리거 뷰만 바뀜, a11y 라벨은 기존 버튼 문구 재사용. +- **작은 화면 대응**: 화면 높이<220pt(40·41mm)는 컴팩트 메트릭(아이콘 54·간격 6) — 40mm에서도 힌트까지 스크롤 없이 한 화면. 힌트는 lineLimit 1+minimumScaleFactor 0.75(ja "アイコンをタップで計測終了"가 40mm에서 두 줄로 꺾여 폴드 밑으로 가던 실측 수정 — 일기 건강 칩 전례 문법). +- **검증**: 신규 인자 `-watchSeedSnapshot`(페어링 없는 합성 스냅숏 — 3상태)+`-watchOpenActionIndex`로 **49mm(울트라)·46mm(26.2)·40mm SE(11.5) × 횟수형/측정 중/대기 + ja** 스크린샷 전부 잘림 0(임시 심은 QA 후 삭제). 도움말 문구 갱신+전 카탈로그 missing/stale 0, 워치·iOS Debug·Store 빌드 그린. 스키마·빌드 번호 무변경(9 유지 — ASC 업로드 전). diff --git a/myApp/HaruDanim/Haru_DanimWatch Watch App/ContentView.swift b/myApp/HaruDanim/Haru_DanimWatch Watch App/ContentView.swift index 40307af..f39af10 100644 --- a/myApp/HaruDanim/Haru_DanimWatch Watch App/ContentView.swift +++ b/myApp/HaruDanim/Haru_DanimWatch Watch App/ContentView.swift @@ -44,12 +44,16 @@ struct ContentView: View { .onAppear { store.activate() } #if DEBUG // -watchOpenActionDetail YES: 스냅숏 수신 후 첫 옵트인(없으면 첫) 행동의 미니 화면을 - // 딥링크와 같은 경로로 push — 시뮬레이터에서 컴플리케이션 탭 착지를 검증하기 위한 훅 + // 딥링크와 같은 경로로 push — 시뮬레이터에서 컴플리케이션 탭 착지를 검증하기 위한 훅. + // -watchOpenActionIndex N을 함께 주면 배치순 N번째(0부터) 행동을 연다 (상태별 QA용, 1.5(9)) .onChange(of: store.snapshot?.generatedAt) { guard !didAutoOpenDetail, UserDefaults.standard.bool(forKey: "watchOpenActionDetail"), let snapshot = store.snapshot else { return } - let target = snapshot.actions.first { $0.complicationPeriodRaw != nil } ?? snapshot.actions.first + let index = UserDefaults.standard.integer(forKey: "watchOpenActionIndex") + let target: WatchActionInfo? = snapshot.actions.indices.contains(index) && index > 0 + ? snapshot.actions[index] + : snapshot.actions.first { $0.complicationPeriodRaw != nil } ?? snapshot.actions.first guard let target else { return } didAutoOpenDetail = true path.append(target.id) diff --git a/myApp/HaruDanim/Haru_DanimWatch Watch App/Localizable.xcstrings b/myApp/HaruDanim/Haru_DanimWatch Watch App/Localizable.xcstrings index b96cb29..1d8cb07 100644 --- a/myApp/HaruDanim/Haru_DanimWatch Watch App/Localizable.xcstrings +++ b/myApp/HaruDanim/Haru_DanimWatch Watch App/Localizable.xcstrings @@ -553,6 +553,54 @@ } } }, + "아이콘을 누르면 1회 추가": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tap the icon to add 1" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アイコンをタップで1回追加" + } + } + } + }, + "아이콘을 누르면 측정 시작": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tap the icon to start" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アイコンをタップで計測開始" + } + } + } + }, + "아이콘을 누르면 측정 종료": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tap the icon to stop" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アイコンをタップで計測終了" + } + } + } + }, "앱을 열면 새로고침돼요": { "localizations": { "en": { diff --git a/myApp/HaruDanim/Haru_DanimWatch Watch App/WatchActionDetailView.swift b/myApp/HaruDanim/Haru_DanimWatch Watch App/WatchActionDetailView.swift index f157e3a..76db46c 100644 --- a/myApp/HaruDanim/Haru_DanimWatch Watch App/WatchActionDetailView.swift +++ b/myApp/HaruDanim/Haru_DanimWatch Watch App/WatchActionDetailView.swift @@ -5,15 +5,25 @@ // 미니 행동 화면 (1.5 — Docs/plan-1.5.md §2) // '행동 기록' 컴플리케이션 탭의 딥링크 착지 지점. // - 워치 앱 첫 화면 위에 push되어 뒤로가기 한 번이면 메인으로 복귀 (사용자 요구) -// - 자동 실행 없음: 큼직한 버튼을 눌러야 실행 (손목 오탭 방지 — 명시적 탭 문법) +// - 자동 실행 없음: 명시적 탭으로만 실행 (손목 오탭 방지 문법) +// - 1.5(9) 재구성(사용자 실기기 실측 — 울트라에서도 하단 버튼이 살짝 잘림): +// 하단 실행 버튼 줄을 없애고 **아이콘 자체가 실행 버튼**이다. 우하단 배지(+/▶/■)가 +// 탭 동작을 알리고, 아래 한 줄 힌트가 상태별 동작(시작/종료/1회 추가)을 설명한다. +// 측정 중 시그널은 앱 전체 문법 그대로: 노란 테두리 + 노란 정지 배지 + 노란 타이머. +// 이 구성은 가장 작은 워치(40mm)에서도 스크롤 없이 한 화면에 들어간다. // import SwiftUI +import WatchKit struct WatchActionDetailView: View { @State private var store = WatchStore.shared let actionID: UUID + /// 작은 워치(40·41mm) 컴팩트 메트릭 — 힌트 줄까지 스크롤 없이 한 화면에 들어가도록 + /// 아이콘·간격만 살짝 줄인다 (화면 높이 기준 분기 — 기기별 고정값이라 결정적) + private var compact: Bool { WKInterfaceDevice.current().screenBounds.height < 220 } + /// 최신 스냅숏 기준의 행동 (실행 직후 응답 스냅숏으로 자동 갱신) private var action: WatchActionInfo? { store.snapshot?.actions.first { $0.id == actionID } @@ -31,30 +41,23 @@ struct WatchActionDetailView: View { } private func detail(_ action: WatchActionInfo) -> some View { + // ScrollView는 안전망(연결 실패 문구·큰 글자 설정)일 뿐, 이 구성은 스크롤 없이 들어간다 ScrollView { - VStack(spacing: 10) { - Image(safeSymbol: action.symbolName) - .font(.system(size: 22, weight: .semibold)) - .foregroundStyle(.white) - .frame(width: 44, height: 44) - .background( - Color(watchHex: action.colorHex), - in: RoundedRectangle(cornerRadius: 12, style: .continuous) - ) + VStack(spacing: compact ? 6 : 8) { + iconButton(action) Text(action.name) .font(.headline) .multilineTextAlignment(.center) .lineLimit(2) valueBlock(action) - Button { - store.run(action) - } label: { - Label(buttonTitle(action), systemImage: buttonSymbol(action)) - .font(.system(size: 15, weight: .semibold)) - .frame(maxWidth: .infinity) - } - .buttonStyle(.borderedProminent) - .tint(action.isRunning && !action.isCount ? .yellow : Color(watchHex: action.colorHex)) + // 아이콘=버튼임을 알리는 상태별 한 줄 힌트 (시작/종료/1회 추가). + // ja처럼 긴 로케일이 40mm에서 두 줄로 꺾여 폴드 밑으로 내려가지 않게 + // 한 줄 고정 + 살짝 축소 허용 (일기 건강 칩과 같은 문법 — 1.5(4) 실측 전례) + Text(hintText(action)) + .font(.caption2) + .foregroundStyle(.secondary) + .lineLimit(1) + .minimumScaleFactor(0.75) if store.lastSendFailed { Text("iPhone에 연결할 수 없어요. 근처에 있는지 확인해 주세요.") .font(.footnote) @@ -62,10 +65,62 @@ struct WatchActionDetailView: View { .multilineTextAlignment(.center) } } + .frame(maxWidth: .infinity) .padding(.horizontal, 4) } } + /// 아이콘 = 실행 버튼. 우하단 배지(+/▶/■)가 탭 동작을, 노란 테두리·배지가 측정 중을 알린다 + private func iconButton(_ action: WatchActionInfo) -> some View { + let runningTime = action.isRunning && !action.isCount + let side: CGFloat = compact ? 54 : 64 + return Button { + store.run(action) + } label: { + Image(safeSymbol: action.symbolName) + .font(.system(size: compact ? 22 : 26, weight: .semibold)) + .foregroundStyle(.white) + .frame(width: side, height: side) + .background( + Color(watchHex: action.colorHex), + in: RoundedRectangle(cornerRadius: 16, style: .continuous) + ) + .overlay { + if runningTime { + RoundedRectangle(cornerRadius: 16, style: .continuous) + .strokeBorder(.yellow, lineWidth: 2.5) + } + } + .overlay(alignment: .bottomTrailing) { + actionBadge(action, runningTime: runningTime) + } + // 배지가 프레임 밖으로 살짝 나가므로 히트 영역은 타일 전체로 명시 + .contentShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) + } + .buttonStyle(.plain) + .padding(.top, 2) + .accessibilityLabel(Text(buttonTitle(action))) + } + + /// 우하단 동작 배지 — 대기: 흰 원 + 행동 색 ▶/+, 측정 중: 노란 원 + 검정 ■ + private func actionBadge(_ action: WatchActionInfo, runningTime: Bool) -> some View { + Image(systemName: buttonSymbol(action)) + .font(.system(size: 10, weight: .bold)) + .foregroundStyle(runningTime ? Color.black : Color(watchHex: action.colorHex)) + .frame(width: 20, height: 20) + .background(runningTime ? Color.yellow : Color.white, in: Circle()) + .overlay(Circle().strokeBorder(Color.black.opacity(0.15), lineWidth: 0.5)) + .offset(x: 5, y: 5) + } + + /// 아이콘 아래 한 줄 힌트 — 상태별 동작 안내 (아이콘이 버튼임을 알리는 유일한 문구) + private func hintText(_ action: WatchActionInfo) -> String { + if action.isCount { return String(localized: "아이콘을 누르면 1회 추가") } + return action.isRunning + ? String(localized: "아이콘을 누르면 측정 종료") + : String(localized: "아이콘을 누르면 측정 시작") + } + /// 설정 기간(컴플리케이션 옵트인)이 있으면 그 기간, 없으면 오늘 기준 누적 @ViewBuilder private func valueBlock(_ action: WatchActionInfo) -> some View { @@ -87,11 +142,8 @@ struct WatchActionDetailView: View { Text(WatchFormat.duration(period != nil ? (action.periodValue ?? 0) : action.todayValue)) .font(.system(.title3, design: .rounded).weight(.bold).monospacedDigit()) } - if action.isRunning, !action.isCount { - Label("측정 중", systemImage: "record.circle") - .font(.caption2) - .foregroundStyle(.yellow) - } + // 예전의 별도 '측정 중' 줄은 제거 — 노란 테두리·정지 배지·노란 타이머·힌트 문구가 + // 같은 정보를 이미 사중으로 전달하고, 작은 워치의 세로 여유가 더 값지다 (1.5(9)) } } diff --git a/myApp/HaruDanim/Haru_DanimWatch Watch App/WatchStore.swift b/myApp/HaruDanim/Haru_DanimWatch Watch App/WatchStore.swift index be2ac04..04ac5a8 100644 --- a/myApp/HaruDanim/Haru_DanimWatch Watch App/WatchStore.swift +++ b/myApp/HaruDanim/Haru_DanimWatch Watch App/WatchStore.swift @@ -30,6 +30,14 @@ final class WatchStore: NSObject { static let groupDefaults = UserDefaults(suiteName: "group.com.yechan.HaruDanim") ?? .standard func activate() { + #if DEBUG + // -watchSeedSnapshot YES: 페어링된 iPhone 없이도 화면을 검증할 수 있는 합성 스냅숏 + // (1.5(9) — 미니 행동 화면 재구성 QA용. -watchOpenActionDetail과 조합해 착지까지 자동) + if UserDefaults.standard.bool(forKey: "watchSeedSnapshot") { + apply(Self.debugSeedSnapshot()) + return + } + #endif guard WCSession.isSupported() else { return } let session = WCSession.default session.delegate = self @@ -114,6 +122,42 @@ final class WatchStore: NSObject { } #if DEBUG + /// -watchSeedSnapshot용 합성 스냅숏 — 미니 행동 화면의 세 상태(횟수형·시간형 대기· + /// 시간형 측정 중)를 페어링 없이 렌더한다. 옵트인 순서: 횟수형(주간) → 측정 중 시간형(하루) + private static func debugSeedSnapshot() -> WatchSnapshot { + let countID = UUID(uuidString: "AAAAAAAA-0000-0000-0000-000000000001")! + let runID = UUID(uuidString: "AAAAAAAA-0000-0000-0000-000000000002")! + let idleID = UUID(uuidString: "AAAAAAAA-0000-0000-0000-000000000003")! + var snapshot = WatchSnapshot() + snapshot.isPremium = true + snapshot.actions = [ + WatchActionInfo( + id: countID, name: "팔굽혀펴기", symbolName: "dumbbell.fill", + colorHex: "#7A5C9D", isCount: true, todayValue: 20, isRunning: false, + tickingBase: nil, tagIDs: [], isFavorite: true, + complicationPeriodRaw: "week", periodValue: 62 + ), + WatchActionInfo( + id: runID, name: "달리기", symbolName: "figure.run", + colorHex: "#9D5B4A", isCount: false, todayValue: 40 * 60, isRunning: true, + tickingBase: Date.now.addingTimeInterval(-40 * 60), tagIDs: [], isFavorite: true, + complicationPeriodRaw: "day", periodValue: 40 * 60, + periodTickingBase: Date.now.addingTimeInterval(-40 * 60) + ), + WatchActionInfo( + id: idleID, name: "독서", symbolName: "book.fill", + colorHex: "#2F6B4F", isCount: false, todayValue: 25 * 60, isRunning: false, + tickingBase: nil, tagIDs: [], isFavorite: true, + complicationPeriodRaw: "last7", periodValue: 3 * 3600 + 20 * 60 + ), + ] + snapshot.runningName = "달리기" + snapshot.runningSymbol = "figure.run" + snapshot.runningStartAt = Date.now.addingTimeInterval(-40 * 60) + snapshot.runningActionID = runID + return snapshot + } + /// -autoRunFirstAction YES: 스냅숏 수신 후 첫 시간형 행동을 한 번 실행 /// (시뮬레이터에서 워치 탭 → iPhone Live Activity 연동을 CLI로 검증하기 위한 훅) private var didAutoRun = false diff --git a/myApp/HaruDanim/IOS/Localizable.xcstrings b/myApp/HaruDanim/IOS/Localizable.xcstrings index c8fb55c..fee8024 100644 --- a/myApp/HaruDanim/IOS/Localizable.xcstrings +++ b/myApp/HaruDanim/IOS/Localizable.xcstrings @@ -17470,18 +17470,18 @@ } } }, - "행동의 누적 기록을 워치 페이스에 두고 싶다면, 아이폰의 행동 편집에서 '애플워치 컴플리케이션'을 켜고 표시 기간(하루·이번 주·이번 달·지난 7일·지난 30일)을 골라 주세요. 그러면 페이스 편집의 '행동 기록' 목록에 그 행동이 나타나요. 측정 중일 때는 컴플리케이션이 노란 타이머로 바뀌고, 컴플리케이션을 누르면 워치 앱의 그 행동 화면이 열려 큰 버튼으로 바로 시작·종료(횟수형은 +1)할 수 있어요 — 뒤로 가면 워치 앱 첫 화면이에요. 페이스의 숫자는 몇 분 간격으로 갱신되니 방금 누른 기록이 바로 안 보여도 잠시만 기다려 주세요.": { + "행동의 누적 기록을 워치 페이스에 두고 싶다면, 아이폰의 행동 편집에서 '애플워치 컴플리케이션'을 켜고 표시 기간(하루·이번 주·이번 달·지난 7일·지난 30일)을 골라 주세요. 그러면 페이스 편집의 '행동 기록' 목록에 그 행동이 나타나요. 측정 중일 때는 컴플리케이션이 노란 타이머로 바뀌고, 컴플리케이션을 누르면 워치 앱의 그 행동 화면이 열려요 — 화면의 행동 아이콘을 누르면 바로 시작·종료(횟수형은 +1)되고, 뒤로 가면 워치 앱 첫 화면이에요. 페이스의 숫자는 몇 분 간격으로 갱신되니 방금 누른 기록이 바로 안 보여도 잠시만 기다려 주세요.": { "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Want an action's running total on your watch face? In the action editor on iPhone, turn on Apple Watch complication and pick a display period — day, this week, this month, last 7 days or last 30 days. The action then appears in the Action Log list when editing your watch face. While tracking, the complication turns into a yellow timer, and tapping it opens that action's screen in the watch app, where a big button starts or stops (or adds +1 for count types) — going back lands on the watch app's first screen. The number on the face refreshes every few minutes, so give it a moment if a fresh record doesn't show right away." + "value": "Want an action's running total on your watch face? In the action editor on iPhone, turn on Apple Watch complication and pick a display period — day, this week, this month, last 7 days or last 30 days. The action then appears in the Action Log list when editing your watch face. While tracking, the complication turns into a yellow timer, and tapping it opens that action's screen in the watch app — tap the action icon there to start or stop right away (or add +1 for count types), and going back lands on the watch app's first screen. The number on the face refreshes every few minutes, so give it a moment if a fresh record doesn't show up right away." } }, "ja": { "stringUnit": { "state": "translated", - "value": "アクションの累計をウォッチフェイスに置きたいなら、iPhoneのアクション編集で「Apple Watchコンプリケーション」をオンにして、表示期間(1日・今週・今月・過去7日間・過去30日間)を選んでください。するとフェイス編集の「アクションの記録」一覧にそのアクションが現れます。計測中はコンプリケーションが黄色いタイマーに変わり、タップするとウォッチアプリのそのアクション画面が開いて、大きなボタンですぐ開始・終了(回数型は+1)できます — 戻るとウォッチアプリの最初の画面です。フェイスの数字は数分おきに更新されるので、いま押した記録がすぐ見えなくても少しだけ待ってください。" + "value": "アクションの累計をウォッチフェイスに置きたいなら、iPhoneのアクション編集で「Apple Watchコンプリケーション」をオンにして、表示期間(1日・今週・今月・過去7日間・過去30日間)を選んでください。するとフェイス編集の「アクションの記録」一覧にそのアクションが現れます。計測中はコンプリケーションが黄色いタイマーに変わり、タップするとウォッチアプリのそのアクション画面が開きます — 画面のアクションアイコンをタップすると、すぐ開始・終了(回数型は+1)できて、戻るとウォッチアプリの最初の画面です。フェイスの数字は数分おきに更新されるので、いま押した記録がすぐ見えなくても少しだけ待ってください。" } } } diff --git a/myApp/HaruDanim/IOS/Views/HelpView.swift b/myApp/HaruDanim/IOS/Views/HelpView.swift index f86f428..5a43fdc 100644 --- a/myApp/HaruDanim/IOS/Views/HelpView.swift +++ b/myApp/HaruDanim/IOS/Views/HelpView.swift @@ -433,7 +433,7 @@ struct HelpView: View { HelpTopic( symbol: "applewatch.watchface", title: "행동 기록 컴플리케이션", - body: "행동의 누적 기록을 워치 페이스에 두고 싶다면, 아이폰의 행동 편집에서 '애플워치 컴플리케이션'을 켜고 표시 기간(하루·이번 주·이번 달·지난 7일·지난 30일)을 골라 주세요. 그러면 페이스 편집의 '행동 기록' 목록에 그 행동이 나타나요. 측정 중일 때는 컴플리케이션이 노란 타이머로 바뀌고, 컴플리케이션을 누르면 워치 앱의 그 행동 화면이 열려 큰 버튼으로 바로 시작·종료(횟수형은 +1)할 수 있어요 — 뒤로 가면 워치 앱 첫 화면이에요. 페이스의 숫자는 몇 분 간격으로 갱신되니 방금 누른 기록이 바로 안 보여도 잠시만 기다려 주세요." + body: "행동의 누적 기록을 워치 페이스에 두고 싶다면, 아이폰의 행동 편집에서 '애플워치 컴플리케이션'을 켜고 표시 기간(하루·이번 주·이번 달·지난 7일·지난 30일)을 골라 주세요. 그러면 페이스 편집의 '행동 기록' 목록에 그 행동이 나타나요. 측정 중일 때는 컴플리케이션이 노란 타이머로 바뀌고, 컴플리케이션을 누르면 워치 앱의 그 행동 화면이 열려요 — 화면의 행동 아이콘을 누르면 바로 시작·종료(횟수형은 +1)되고, 뒤로 가면 워치 앱 첫 화면이에요. 페이스의 숫자는 몇 분 간격으로 갱신되니 방금 누른 기록이 바로 안 보여도 잠시만 기다려 주세요." ), HelpTopic( symbol: "arrow.triangle.2.circlepath",