재분석에서 나온 잔여 항목 일괄 처리:
1) 판정 하루 키 엣지 (A)
judgmentReference가 종료일 '정오'를 경유해 논리적 하루를 찾던
것을 달력일 키 직접 조회로 변경 — '하루 시작 시간'을 12시 이후로
설정한 경우에도 판정 범위가 전날로 밀리지 않는다.
2) 주기 중간 종료 판정 (B — 설계 결정: 경과일 비례)
종료 판정 전용 QuestProgress.judgment(asOf:) 신설, 판정 평균
(questAchievementRatio)이 current() 대신 사용:
- 값은 기준 시점까지의 누적으로 제한 — 종료일 이후 같은 주기의
기록이 판정에 섞이지 않음
- 주간/월간/특정 기간 다짐의 목표량·한도를 주기 내 경과일 비율로
축소 — 수요일에 끝나는 목표의 주간 다짐이 남은 4일치 미달로
깎이지 않고, '이하 유지' 한도도 같은 비율이라 대칭적으로 공정
- 하루 단위 다짐은 축소 없음 (그날 목표는 온전히 채워야 달성)
- 수동 종료(manualFinish)도 같은 함수를 타므로 주중 수동 종료의
주간 다짐이 공정하게 판정됨. 표시 연산(combinedSpanRatio·
spanProgress)은 불변.
검증: 주간 2시간 다짐 목표를 주 시작일(월)에 종료 →
화요일 기록 40분이 배제되고 비례 목표(≈17분) 기준으로 판정됨을
DB에서 확인 (처음 실패는 '설치 직후 첫 실행 인자 무시' 시뮬레이터
특성이 원인 — 코드 문제 아님, §2.2 문서화된 사항).
3) 양식 렌더 트레이드오프 (C)
- 백그라운드 렌더 실패 시 renderedScale을 되돌려 다음 기회(줌·
재표시)에 재시도되게 함
- 썸네일 겸 첫 표시 플레이스홀더 해상도 320→512px — 본 렌더
도착 전의 흐릿함 완화 (저장 증가는 수십 KB 수준)
- 양식 관리의 사용 중 페이지 수를 행마다 재스캔하지 않고 1회 집계
빌드: Debug·Store·워치 3스킴 성공. CLAUDE.md §4.3에 주기 중간
종료 판정 규칙 추가. 신규 사용자 문구 없음(카탈로그 변경 없음).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
전체 재분석에서 확정한 4건 + 양식 개선 2건:
1) 목표 자동 판정 기준 시점 (핵심 수정)
기존에는 판정이 "실행되는 시점의 현재 주기" 기준이라, 종료일
며칠 뒤에 앱을 열면 목표 기간 밖의 데이터로 달성/미달성이
갈렸다. 이제 자동 판정(evaluateIfEnded)은 **종료일이 속한
논리적 하루의 마지막 순간**(judgmentReference) 기준 — 늦게
실행해도, 종료일을 과거로 고쳐도 종료일까지의 기록으로 판정.
(하루 시작 시간 설정으로 기준이 미래가 되는 경우 now로 상한)
수동 종료는 종료일 없는 목표 전용이므로 기존대로 "지금" 기준.
목표 편집 시 종료일 변경 처리: 진행 중 목표는 저장 즉시 판정,
완료된 목표의 종료일을 바꾸면 '진행 중'으로 재개 후 재판정
(미래/없음 → 진행 중 복귀, 다른 과거 날짜 → 그 시점 기준 재판정).
검증: -endGoalYesterday로 어제 종료 → 종료일 기준 '달성' 판정 확인.
2) 워치 컴플리케이션 지역화 2건
- 다짐 달성률 rectangular의 기간 라벨("하루/주간/월간")이 String
파라미터라 미추출 → en/ja에서 한국어 노출 → String(localized:)
- 빈 상태 문구("목표 없음"/"다짐 없음") 동일 문제 → 추출 + 워치
앱/워치 위젯 카탈로그에 en/ja 번역 추가 (타깃별 stringsdata로
sync — 카탈로그 오염 없음, 전 카탈로그 missing 0)
3) 배포 체크리스트: CloudKit 프로덕션 스키마 배포 항목을 CLAUDE.md
§1에 추가 (DiaryTemplate 등 새 레코드 타입은 출시 전 대시보드
Deploy Schema to Production 필수)
4) 일기 양식 렌더 개선
- 첫 렌더 비동기화: 저장된 썸네일을 플레이스홀더로 즉시 깔고
본 렌더(2×)를 백그라운드로 — 복잡한 벡터 PDF도 페이지 넘김이
걸리지 않음
- 비활성 페이지 고해상도 강등: 화면 밖 페이지는 래스터를 2×로
상한(allowsHighResolution) — TabView가 이웃 페이지를 유지해도
양식 여러 장의 메모리 사용을 절약, 활성 복귀 시 원래 배율 복원
검증: 양식 페이지 2× 프로그램 줌에서 점 노트 선명(회귀 없음),
시드 필기 페이지 획 유지 확인.
빌드: Debug·Store·워치 스킴 모두 성공. 도움말(목표 판정 문구
갱신 — 종료일 기준·재판정 규칙 안내)과 CLAUDE.md §4.3 갱신,
문구 수정 stale 1건 삭제 및 ko/en/ja 번역 완료.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
일기 노트 페이지 양식(속지) 기능 (합의된 설계 그대로):
- 모델: DiaryTemplate 신설(원본 externalStorage — 여러 쪽 PDF도
통째 1개만 저장 + pageCount) + DiaryPage.templateID/
templatePageIndex(기본값 있는 새 필드, uuid 문자열 참조 —
CloudKit 규칙·동기화 순서에 안전). 스키마에 등록.
- 양식 관리: 달력 툴바에서 진입 — PDF(파일, ≤20MB)·사진 가져오기,
이름 변경·삭제(사용 중 페이지 수 경고 → 빈 캔버스 폴백, 필기 보존),
첫 쪽 썸네일 저장. 쪽 상한 24쪽(초과분 안내).
- 페이지 추가: 양식이 있으면 [빈 캔버스/줄 노트/내 양식] 선택 시트,
여러 쪽 PDF는 2단계 쪽 썸네일 선택(지연 생성). 양식이 없으면
기존처럼 즉시 추가 — 흔한 경로의 반응성 유지.
- 렌더: 페이지 논리 좌표(768×1024)에 aspect-fit — 필기 좌표계 불변.
PDF는 벡터 원본을 보존하고 줌 종료 시 현재 배율로 백그라운드
재래스터(펜슬 캔버스 선명도 훅에 함께 연결 — 확대해도 흐림 없음).
이미지는 가져올 때 긴 변 3072px 리샘플(필기 래스터 상한 3×와 동일).
양식 페이지는 줄 노트 토글 숨김(상호 배타), 양식만 깔린 페이지도
작성함 취급(hasContent). PencilKit 캔버스 코드는 건드리지 않음.
- 내보내기(PDF/PNG)에도 양식 배경 동일 반영.
검증(iPad 시뮬레이터, -diarySeedTemplate 등 신규 DEBUG 인자):
양식 관리·페이지 선택 시트·양식 페이지 렌더(2× 프로그램 줌에서
점 노트 선명) 스크린샷 + 이미지 내보내기 산출물에 양식 포함 확인.
Debug·Store 빌드 성공, 신규 문자열 22종 ko/en/ja(missing 0).
도움말 '나만의 양식(속지)' 항목 추가, CLAUDE.md §3·§6.7·§14 갱신.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
일기 요약 페이지 개선 2건:
1) 캘린더 일정 블록 가시성
기존 옅은 채움(0.14)+실선 외곽선은 배경과 겹쳐 잘 안 보였다.
구분 방향은 유지하되(꼬리표 색과 캘린더 색이 같아도 헷갈리지 않게)
구분 수단을 '농도'에서 '스타일'로 변경 — 채움을 0.3으로 올려
또렷하게 하고, 행동 블록(불투명 채움)과는 점선 테두리(1.5pt)로
구분한다. 화면과 일기 내보내기(PDF/PNG)가 같은 뷰를 쓰므로 함께 반영.
2) '이 날의 목표' 필터
카드 우상단에 타임테이블 필터와 같은 모양의 필터 버튼 추가 —
그날 진행 중인 목표 중 보고 싶은 것만 선택(숨긴 개수 노란 배지).
선택은 날짜별로 DiaryEntry.hiddenGoalIDs(uuid 문자열, 기본값 있는
새 필드 — CloudKit 규칙 준수)에 저장되어 기기 간 동기화에 안전.
시트는 전체 선택/해제 + 개별 체크(캘린더 일정 선택과 동일 패턴),
열람 전용(프리미엄 만료)에서는 버튼 숨김, 빈 엔트리 정리 조건에도
목표 필터 선택 보존 추가. 모두 숨기면 안내 문구 표시.
검증: Debug·Store 빌드 성공, iPad 시뮬레이터에서 점선 캘린더 블록·
필터 버튼·팝업(첫 목표 숨김 → 카드 제외 + 배지 1) 스크린샷 확인.
신규 문자열 ko/en/ja 번역(missing 0, 문구 수정 stale 1건 삭제).
DEBUG 인자 -diaryHideFirstGoal/-diaryShowGoalFilter 추가.
도움말(일정·필터 항목)과 CLAUDE.md(§3·§6.7·§14) 갱신.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
글래스 버블 메뉴가 닫힌 상태에서 FAB를 0.35초 안에 빠르게 두 번 누르면
설정한 탭으로 바로 이동하는 옵션. 첫 탭을 지연시키는 더블탭 제스처 인식은
메뉴 열림 반응성을 해치므로, 첫 탭은 즉시 메뉴를 열고 두 번째 탭에서만
분기한다 — 더블탭 시 버블이 피어나다 FAB로 흡수되며 화면이 바뀌는 연출.
- 설정(내비게이션, 버블 모드 전용): '두 번 눌러 바로 이동' 토글 +
'이동할 탭' 피커 + 푸터 설명. settings.radialDoubleTapTab
(rawValue, 빈 값=꺼짐, 기기 로컬)
- 검증용 런치 인자 -radialDoubleTapTest (2초 뒤 더블탭 재현) —
통계 탭 이동 스크린샷으로 확인
- 도움말: 버블 메뉴 항목에 더블탭 문장 추가 + 누락돼 있던
'홈 화면·잠금화면 위젯' 사용법 항목 신설
- l10n 6개 문자열 ko/en/ja, CLAUDE.md §6·§6.8·§14 갱신
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
③ 다짐 진행률 위젯에서 '이하 유지' 다짐은 시작부터 링이 가득 차 있고
문구도 '오늘 한도 지킴'뿐이라 지금 몇 회/몇 시간 했는지 알 수 없던 문제.
퍼센트(또는 한도 지킴/초과) 문구와 연속 달성 문구 사이에 위젯 옵션의
진행률 기간 기준 실제 누적값 한 줄을 추가한다 ("1시간 20분" / "3회").
'이상 달성' 다짐에도 동일하게 표시. 주간·월간 진행률 캡과 무관한
원본 누적값이며, 다른 위젯(②④)과 나머지 레이아웃은 그대로.
- QuestCellSnapshot.valueLabel 추가 (spanRange 원본 값 포맷)
- QuestProgress.spanRange(_:now:) 헬퍼 추출
- 위젯 미리보기에 '이하 유지(소형)' 행 신설 (앵커 CM)
- l10n: '0분' ko/en/ja (IOS·Widgets 카탈로그)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
'이하 유지' 다짐은 기록 없는 날도 달성으로 집계되므로 연속 계산이
목표가 생기기 전 과거(스캔 상한 400일)까지 거슬러 올라가 '연속 401일'로
표시되던 버그 수정. 연속은 행동이 아니라 '목표 안 다짐'의 달성이므로
하한 = 목표 시작일:
- 하루 단위: 시작일이 속한 논리적 하루부터만 센다
- 주간/월간: 시작일이 걸친 부분 주기까지 포함, 그 이전 주기에서 중단
검증: 시드에 재현 목표(스크린 타임 관리, 3일 전 시작 + 초과 없는 이하
유지 다짐) 추가 → 기대값 연속 4일 확인. -streakDump 런치 인자 신설
(전 다짐의 연속 계산 결과를 Documents/streak-dump.txt로 덤프).
부수: 도움말에 '연속 달성 표시' 항목 추가(ko/en/ja). 지난 기능들의
문자열이 위젯·워치·InfoPlist 카탈로그에 미번역으로 남아 있던 것을
채움(제어 센터·연속 표기·즐겨찾기·캘린더 권한 문구) — l10n 루틴이
IOS 카탈로그만 확인하던 함정을 CLAUDE.md §2.3에 명시.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
제어 센터/잠금화면 하단/액션 버튼에 배치할 수 있는 '행동 실행' 컨트롤:
- 행동 하나를 골라(미선택 시 배치 첫 행동) 시간형은 시작/종료 토글,
횟수형은 탭마다 +1 (토글 표시는 꺼짐으로 복귀)
- 전용 ToggleActionControlIntent(SetValueIntent + LiveActivityIntent) —
앱 프로세스 실행이라 제어 센터에서 시작해도 다이나믹 아일랜드가 바로 뜸.
단축어 앱에는 숨김(isDiscoverable=false), 프리미엄 게이트 적용
- DataChange.commit / IntentStore.performWrite에
ControlCenter.reloadAllControls() 추가 — 앱/위젯/시리/워치 어느 경로로
측정 상태가 바뀌어도 컨트롤 토글 표시가 따라온다
시뮬레이터에는 제어 센터에 컨트롤을 추가할 수단이 없어 빌드·인텐트
메타데이터 등록 확인까지 검증 (실기기에서 배치·토글 확인 필요).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- 텍스트 상자 요소: 노트 페이지 툴바에 '텍스트' 추가 — 키보드로 쓰는
글 상자를 배치 요소로 올린다 (배치 모드에서 이동·크기 조절, 선택 시
'글 수정'으로 내용·색 편집, 내보내기에도 그대로 렌더).
DiaryPageItem에 text 필드 추가(kind .text, CloudKit 기본값 규칙 준수)
- 페이지 순서 변경: ⋯ 메뉴 → '페이지 순서 변경' 시트에서 노트 페이지를
드래그로 재배열 (요약 페이지는 항상 첫 장)
- 프리미엄 만료 후 열람 허용: 이미 쓴 일기가 있으면 잠금 화면 대신
달력을 열람 전용으로 연다 — 안내 배너 + 일기 있는 날짜만 활성,
진입만으로 엔트리를 만들지 않고, 기분/할일/필기/필터/캘린더 등
모든 편집 UI 비활성 (environment diaryReadOnly로 전파).
일기를 쓴 적 없는 미구매 사용자는 기존 잠금 안내 유지
검증(iPad): 시드된 노트 페이지에서 텍스트 상자 렌더·툴바 확인,
-premium NO에서 배너/날짜 비활성/편집 UI 숨김 스크린샷 확인
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
다짐 단위 '연속 달성' 표기 추가 (QuestProgress.streak → QuestStreakInfo):
- 하루 단위 다짐: 적용일만 세는 연속 계산 — 매일이면 연속 일수,
'매주 수요일'이면 수요일들만 이어서 센다 (4주 연속 = 연속 4일)
- 주간/월간 다짐: 주기 전체 달성 여부로 연속 N주/N달
- 특정 기간 다짐: 반복이 없어 표시하지 않음
- 진행 중인 오늘/이번 주기는 '이상 달성'이면 미달이어도 끊기지 않고
건너뛰며, 채웠으면 포함. '이하 유지'는 한도를 넘는 순간 끊김
- 성능: 기록을 논리적 하루 키로 1회 버킷팅해 O(기록수+일수),
소급 상한 400일
표기(기존 디자인·데이터는 그대로 두고 추가만, 0이면 숨김):
- 목표 탭 다짐 행: 이름 옆 불꽃 + "연속 N일" 배지 (노랑)
- 위젯 ③ 다짐 진행률: 퍼센트 아래 한 줄
- 위젯 ④ 다짐 현황: 이름 아래 작은 글씨
- 위젯 ② 목표+다짐 구성: 퍼센트 아래 6.5pt — 연속 표기가 있을 때만
행 높이 14→19 (적응 채움이 자동 반영)
- 문구는 '스트릭' 대신 "연속 N일/주/달" (en은 streak, ja는 N日連続)
문구 정정: '몇째 주 요일' → '몇 번째 요일' — 구현(weekdayOrdinal)은
달력의 주차가 아니라 "그 달의 n번째 ◯요일"이므로 편집 화면 픽커와
주기 요약 문구를 실제 동작에 맞게 수정 (동작 변경 없음). en/ja 번역 포함.
검증: 시드 데이터로 목표 탭 배지 손계산 대조(독서 연속 3일 — 3일 전
기록 없음 / 영상 시청 어제 한도 초과로 연속 1일 등 전부 일치),
위젯 ②③④ 미리보기 스크린샷으로 레이아웃 넘침 없음 확인,
missing: [], Debug·Store 스킴 빌드 성공.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
RunActionIntent(위젯 실행 버튼)도 LiveActivityIntent를 채택 — 시스템이
앱 프로세스에서 실행해, 위젯 탭으로 시간 측정을 시작해도 시리 경로와
동일하게 다이나믹 아일랜드가 바로 뜬다.
과거 '위젯 버튼 먹통' 증상 때문에 확장 프로세스 고정을 유지했었으나,
해당 증상은 위젯 재추가·기기 재부팅(캐시)으로 해소된 것으로 확인되어
제약을 풀었다. 실기기에서 응답성 회귀가 보이면 이 인텐트의
LiveActivityIntent 채택 한 줄만 되돌리면 된다 (코드 주석에도 기록).
검증: -intentSmokeTest에 위젯 실행 토글 시나리오 추가(시작 → 토글 종료),
10개 시나리오 전부 통과. Debug·Store 스킴 빌드 성공.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
시리 단축어 점검에서 나온 추천 수정 일괄 적용:
- StartTimeActionIntent·StopTimeActionIntent가 LiveActivityIntent 채택 —
시리/단축어의 백그라운드 실행에서도 Live Activity 시작이 허용되어
앱을 열지 않아도 다이나믹 아일랜드에 측정이 표시된다.
RunActionIntent(위젯 버튼)는 확장 프로세스 응답성 튜닝을 유지하기 위해
의도적으로 제외.
- 행동 이름 파라미터 문구 추가: "시리야, 하루 다님에서 독서 측정 시작"처럼
되묻지 않고 한 번에 실행. 어휘 갱신을 위해 앱 시작 시
updateAppShortcutParameters() 호출 (새 행동은 다음 실행부터 인식).
- IOS/AppShortcuts.xcstrings 신설 — 시리 문구 ko/en/ja 지역화.
빌드 로그의 --no-app-shortcuts-localization가 사라지고 root.ssu.yaml에
en/ja 발화가 포함되는 것 확인.
- RunActionIntent isDiscoverable=false — 생 UUID 파라미터라 사용자가 조합할
수 없는 위젯 전용 동작을 단축어 앱·스포트라이트에서 숨김 (위젯 버튼은
그대로 동작).
- 결제 화면 기능 목록에 '시리 단축어' 추가. 이 과정에서 featureRow가
String 파라미터라 기능 목록 6줄 전체가 미지역화였던 버그 발견 —
LocalizedStringKey로 바꾸고 en/ja 12개 문자열 번역.
검증: -intentSmokeTest 9개 시나리오 전부 통과(LiveActivityIntent 전환 후
재확인), 프리미엄 화면 렌더링 확인, Debug·Store 스킴 빌드 성공.
남은 확인: 시리 음성 인식·백그라운드 Live Activity 실표시는 실기기 필요.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
CLAUDE.md v2.0 — 기획서(v1.0)를 폐기하고 실제 구현 기준의 현행 명세로 전면 재작성:
프로젝트 구조·빌드/검증 루틴·도메인 모델(CloudKit 규칙)·집계/판정 규칙·데이터 계층
(LocalPrefs/DataChange)·화면별 상세·내보내기·프리미엄/StoreKit·위젯 6종·워치·시리·
DEBUG 런치 인자·작업 컨벤션까지 새 세션이 이 문서만으로 파악 가능하게 정리.
동작 보존 범위의 안전 수정 3건:
- Color.hexString: 와이드 컬러(P3) 성분을 0...1로 클램프 — 음수/1 초과 값이
%02X를 거치며 자릿수 깨진 hex가 태그·목표 색으로 저장되던 잠재 버그 방지
- 일기 타임테이블 필터: 선택 즉시 명시적 save — 캘린더 일정 선택 경로와 동일하게
영속을 보장 (autosave 의존 제거)
- 통계 평균 "%.1f회"가 String(format:)이라 영어·일본어에서도 '회'로 노출되던 것을
Format.countAverage로 지역화 (en "times" / ja "回", 카탈로그 동기화 포함)
Debug·Store(Release) 스킴 빌드 검증 완료.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
일기 타임테이블에서 그날 기록된 행동 중 원하는 것만 골라 볼 수 있게.
캘린더 일정 선택과 마찬가지로 날짜별로 저장되고 내보내기에도 반영된다.
- DiaryEntry.hiddenActionIDs (행동 uuid 문자열, 날짜별·동기화 안전)
- 타임테이블 카드 우상단에 필터 버튼 추가 (숨긴 개수 배지, 캘린더 버튼 옆)
- 기존 RecordFilterSheet 재사용 — 그날 기록된 행동만 꼬리표별로 나열
- 필터는 타임테이블에만 적용, 기록 목록·요약 통계는 전체 유지
(ExportSnapshot.replacingTimetableSections로 내보내기도 동일 규칙)
- 도움말 갱신: 목표 달성 판정 기준, 일기 타임테이블 일정·필터 항목
- en/ja 번역 추가 (Live Activity의 미번역 문자열 3건도 함께 보충)
- 검증용 런치 인자 -diaryShowActionFilter, -diaryHideFirstAction 추가
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
목표 종료 판정이 '다짐 전체 달성'으로 고정되어 있던 것을, 목표를 만들 때
달성 판정 기준(%)을 정할 수 있게 확장. 다짐들의 평균 달성률(달성=100%,
미달성=현재 주기 진행률)이 기준 이상이면 달성 완료로 판정한다.
- Goal.achieveThresholdPercent (기본 100 = 기존 동작 유지, CloudKit 호환)
- evaluateIfEnded/manualFinish가 questAchievementRatio ≥ 기준으로 판정
- 목표 추가/수정 화면에 '달성 판정 기준' 슬라이더 (10~100%, 5% 단위)
- 수동 종료 안내 문구에 기준이 100% 미만이면 해당 기준 표기
- 검증용 런치 인자 -goalShowEditor 추가
- 그 외 진행률 표시·연산은 변경 없음
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- DiaryEntry.calendarEventIDs: 날짜별로 어떤 캘린더(EventKit) 일정을 넣을지
따로 저장 — 오늘은 포함, 내일은 미포함 같은 선택이 가능
- 타임테이블 카드 우상단 캘린더 버튼(+선택 개수 배지) → 일정 선택 팝업:
그날의 일정 목록에서 전체 선택/해제 또는 개별 체크, 권한 미허용 시 설정
이동 안내. 종일 일정은 제외, 하루 경계에 걸치면 잘라서 표시
- 일정은 행동 블록과 구분되는 외곽선+은은한 채움 스타일(캘린더 색)로 렌더,
일기 내보내기(PDF/이미지)에도 동일하게 포함 — 내보내기 요약도 화면과
같은 24시간 타임테이블로 통일
- NSCalendarsFullAccessUsageDescription 추가, 새 문자열 en/ja 번역
- 검증 인자: -diarySeedEvents YES, -diaryShowCalendarPicker YES
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- 'Haru_Danim-Store' 공유 스킴 추가: Release 구성으로 실행해 DEBUG 코드
(프리미엄 테스트 토글·검증용 런치 인자)가 전부 빠진 '스토어에 올릴
그대로'의 앱을 실기기에서 확인. StoreKit 로컬 구성은 유지되어 실제 결제
시트가 뜨되 돈은 청구되지 않는다
- 실구매/복원 시 DEBUG 테스트 토글 강제 모드를 자동 해제(실권한 우선),
개발용 섹션에 '토글 강제 해제' 버튼과 동작 설명 footer 추가
- Localizable.xcstrings는 Xcode 직렬화 포맷 정규화
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- 첫 페이지를 '하루 정리' 화면으로 개편: 기록 유무와 무관한 24시간 고정
타임테이블(trimHours 옵션), 그 날짜에 진행 중이던 목표들의 다짐별 진행
현황 카드(하루 다짐은 그날 실적, 주간/월간/기간 다짐은 그 날짜까지 누적,
'이하 유지'는 한도 게이지로 구분), 기록 상세·통계 막대 재배치
- 노트 페이지: UIScrollView 기반 핀치줌(두 손가락, fit~4배, 배치 모드에선 잠금)
- 줄 노트 줄 간격 설정(좁게/보통/넓게/아주 넓게, 페이지별 저장·내보내기 반영)
- 새 문자열 en/ja 번역 추가
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
일기 기능의 스키마 변경(모델 4종 추가) 후 기존 데이터가 있는 기기에서 첫 실행 시
마이그레이션이 필요한데, 이때 위젯 프로세스와의 스토어 경합이나 마이그레이션 실패가
발생하면 makeContainer()가 곧장 fatalError로 죽어 스플래시도 못 띄우는 문제 방어:
- 일시적 경합 대비 최대 3회 재시도(점증 대기)
- 그래도 실패하면 스토어를 타임스탬프 백업으로 보존 후 새로 생성해 앱은 반드시 켜지게
- 검증: 스토어 파일 고의 손상 → 백업 생성 + 정상 부팅 확인
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- 달력(작성일 마커: 기분 이모지/연필 점) → 날짜별 일기, 이번 달 작성 수 표시
- 첫 페이지: 날짜·오늘 기분(이모지/사진, 끄기 옵션)·할 일 체크리스트(스크리블 입력)·
타임테이블·통계(내보내기 스냅숏 재사용으로 화면·내보내기 일치)
- 노트 페이지: PencilKit 캔버스(도구 팔레트) + 줄 노트 배경 + 사진/도형(사각형·원·화살표·선)
배치 모드(드래그·핀치), 페이지 무한 추가 — 논리 크기 768×1024 고정으로 WYSIWYG 내보내기
- 내보내기: 구간/복수 날짜(MultiDatePicker) → PDF 1개 또는 날짜별 세로 스티치 PNG
- SwiftData 모델 4종(CloudKit 호환 패턴) 스키마 추가, PremiumFeature.diary,
iPhone 탭 컨텍스트(탭바·더보기·라디얼)에서 일기 제외, en/ja 번역 39건
- 검증 인자: -diarySeed/-diaryOpenToday/-diaryPage N/-diaryShowExport/-diaryExportRun pdf|image
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
The vertical capsule stack reached ~414pt up the screen, putting the top
tabs out of thumb range. Replace it with 'Glass Bubbles': liquid-glass
orbs that fan out from the FAB along a semicircular arc whose radius
(~152pt, clamped to screen width) keeps every tab inside the natural
thumb sweep from the bottom-center pivot.
- Each bubble: ultraThinMaterial circle + top white gradient (specular
light) + white 0.4 hairline + soft shadow; label with shadow below.
Current tab rendered as green glass with white symbol.
- Entrance: bubbles fly from the FAB to their arc slot while rotating
-40deg -> 0 with a snappy spring (response 0.32, damping 0.72) and a
per-item 0.03s stagger, like droplets bursting in sequence; collapse
snaps back with no delay. All transform-only (offset/scale/opacity/
rotation) so no per-frame blur recomputation.
- Struct renamed FloatingCapsuleMenu -> GlassBubbleMenu; storage key and
NavStyle.radial rawValue unchanged for compatibility.
- Settings copy updated: nav style now '글래스 버블 메뉴' with a
description of the fan-out and one-handed reach; order editor renamed
'버블 메뉴 순서' with left-to-right arc wording.
Verified: clean rebuild (stale incremental link produced an old binary
despite BUILD SUCCEEDED; wiped DerivedData), expanded-arc screenshot
shows all 7 bubbles within thumb radius with legible labels, and the
settings screen shows the new name/description.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
Widget system rebuilt as 5 independent widgets, each with its own
WidgetConfigurationIntent registered in the WidgetBundle:
- ① ActionRun: multi-action selection, value display option (day/week/
month/hidden), full tag-color cells; small 1 / medium 4 (2x2) / large
4 big cells or 8 compact cells (2x4) via largeStyle. Unfilled slots
render as dashed blank cells.
- ② GoalProgress: multi-goal selection; medium [2 goals | goal+top
quests], large [4 goals | 2x goal+quests | 1 goal + all quests] via
medium/largeStyle. New QuestBarRow / GoalWithQuestsCellView.
- ③ QuestRing: multi-quest selection + span; surface-card ring (tag
color) with centered icon, name and "오늘 N%" text; interactive run
via RunActionIntent; small 1 / medium 2 / large 4 (2x2).
- ④ QuestStatus (display-only): goal selection + span; rings with name
only (no %), goal icon+name top-left; small 4 (2x2) / medium 8 (4x2) /
large [16 (4x4) | 4 goals | 2 goals] via largeStyle.
- ⑤ StatsChart: multi-action selection capped per family (2/4/6) +
3 span modes; same design scaled by family.
Common foundation:
- Theme option revived on every intent: match app / light fixed / dark
fixed (WidgetThemeOption AppEnum) applied through widgetAppTheme(_:)
with proper iOS 17 containerBackground; non-fullColor rendering modes
(tinted/clear/accented) keep the Color.clear + semantic-color defense
so content never goes white-on-white.
- Providers are family-aware (capacity by size) and slot-pad selections
(WidgetStore.slots); defaults fill from existing data so fresh widgets
never look empty.
- Interactive path unchanged: RunActionIntent(actionID:) +
IntentStore.performWrite -> reloadAllTimelines.
Floating menu polish:
- All glass capsules now share a uniform width (longest label wins,
locale-proof) for a tidy popped-up list.
- Settings strings updated: nav style renamed to '플로팅 글래스 캡슐'
with description matching the capsule popup, order editor retitled
'캡슐 메뉴 순서' with bottom-to-top wording.
Verified: BUILD SUCCEEDED (iPhone 17 Pro sim); widget preview harness
extended with per-variant scroll anchors and all 15 layout variants
screenshot-checked, including forced accented mode (legible translucent
cells) and blank-slot rendering; capsule menu and settings text
screenshot-checked.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
Weekly/monthly progress for a daily quest summed each day's raw value into
the numerator, so one over-achieving day leaked its overage into the span
total. Example: two daily quests with a 5-minute target; on one day quest A
did 5 min (100%) and quest B did 10 min (200%). Both are "done" for that day,
but the weekly ratio counted quest B as 600s vs quest A's 300s — B carried
double the weight, and its over-achievement masked other days' shortfalls and
could push per-quest weekly display past 100%.
spanProgress now aggregates a daily 'atLeast' quest's week/month value as the
sum of per-day values each capped at that day's target (min(dayValue, target)),
via a new private spanValue(for:range:now:). This equals averaging per-day
capped ratios, so a single 200% day contributes exactly one day's worth — no
day can cover for another, and per-quest weekly/monthly display never exceeds
100%. Scope is deliberately narrow:
- day span is untouched — a single day still shows 200% over-achievement.
- 'atMost' (stay-under) quests keep their existing per-day 100%/0% logic;
capping the value would make them always pass, so they use the raw value
unchanged — the two rules coexist without contradiction.
- weekly/monthly/custom quests accumulate freely within their period, so no
per-day cap applies to them.
Only the progress calculation changed; raw records are never altered, so the
records/stats screens still show the true 10-minute total.
Verified in the simulator with a temporary isolated in-memory self-test
(removed before commit): quest A (100%) and quest B (200%) produce identical
weekly ratios (0.1429 each), quest B's weekly display stays <= 100%, and its
day-span display remains 200%. Build succeeds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
Two independent widget issues, both confined to the widget/intent layer.
The app→widget real-time sync path (IntentStore.refresh + performWrite +
reloadAllTimelines) is intentionally left untouched.
1. Interactive button taps were unreliable ("touch feedback but nothing
changes; works only after several taps"). The run buttons passed a full
AppEntity parameter — RunActionIntent(action: cell.entity). On every tap
WidgetKit re-resolves that AppEntity through ActionEntityQuery.entities(for:),
which opens a brand-new ModelContainer from disk each time. That heavy
entity-resolution round-trip on the tap hot path is the classic cause of
dropped/laggy interactive-widget taps — the write logic itself was already
proven reliable last session. Replaced RunActionIntent's AppEntity parameter
with a plain UUID String (actionID) that deserializes instantly with no
query; perform() looks the action up inside the existing single-context
performWrite path. One tap now fires immediately. RunActionIntent is
widget-only (not a Siri AppShortcut), so this touches no shortcut. Both call
sites (ActionRunCellView, QuestRingActionCellView) updated; the now-unused
ActionCellSnapshot.entity helper removed.
2. Configuration screens were cluttered — every widget exposed action/goal/
quest slots 1–4 regardless of family, so a Small widget's long-press config
listed slots it never renders. Added a parameterSummary to all six
WidgetConfigurationIntents so the config reads as one clean sentence, and
labeled secondary slots with the size that uses them (e.g. "행동 2 (중형·대형)",
"목표 3 (대형)"). WidgetKit can't hide parameters by family, so this makes the
optional slots self-explanatory instead of noise.
Verified: BUILD SUCCEEDED (app + embedded Haru_DanimWidgets.appex); cold launch
with the widget preview renders without crashing. Physical home/lock-screen
button taps still warrant a real-device pass (the simulator can't tap widget
overlays).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
Interactive home/lock-screen widget buttons (count +1, timer start/stop)
gave touch feedback but often didn't persist, only working after several
rapid taps. Two compounding defects in the widget-extension write path:
- The extension could crash mid-write. refresh()/makeContainer() opens a
new ModelContainer on every intent perform and every timeline read; in
the extension that hit a local-store branch whose failure was fatalError.
A transient App Group store contention with the running main app crashed
the whole appex — the tap looked dead and only a later retry won the race.
Split makeContainer into a throwing makeContainerThrowing(); refresh() now
wraps it in try? and keeps the previous container on failure, so the
extension degrades instead of crashing. The app's DataStore.shared keeps
its crash-on-failure semantics.
- Fetch → mutate → save could span different contexts and save errors were
swallowed. IntentStore.context is a computed container.mainContext, and
commit() re-read it for a try? save(), so a mid-flight container swap left
the mutation on a dead context and any save failure vanished silently.
Added IntentStore.performWrite(_:) which captures one context for the whole
fetch+mutate+save, saves with a retry (no silent try?), then fires the
Live Activity hook and reloadAllTimelines. All four mutating intents
(RunAction/StartTime/StopTime/AddCount) now fetch in that single context.
No @Model or main-app logic changed — fix is confined to the widget/intent
data layer. Verified: 5 rapid RunActionIntent.perform() calls persist +5 with
no lost writes across three cold launches (ok=true each). Build succeeds
(app + embedded Haru_DanimWidgets.appex).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
Radial menu icon order (iPhone floating nav only):
- Settings → 내비게이션 gains a '반원 메뉴 순서 편집' link (shown only when the
radial style is selected) that opens a drag-to-reorder list of all 7 tabs.
- Order is stored device-local via @AppStorage(SettingsKeys.radialTabOrder)
(not CloudKit-synced); RadialMenu reads it through AppTab.radialOrder(from:),
which always yields the full tab set and appends any missing/unknown value in
default spec order — so partial or legacy strings can never drop a tab.
- iPad/Mac sidebars and the watch target are untouched (they never read the key).
History timetable date step:
- The date header </> buttons now jump by a full week (7 days) when the
timetable is in '일주일' view, and by 1 day otherwise (list / daily timetable).
- The calendar popover and '오늘' still set the date directly, so those paths and
record loading are unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
Adds an alternative iPhone navigation style, switchable in Settings, that
replaces the bottom tab bar with a single center floating button which fans
all tabs out into an upper 180° semicircle.
- Settings → 내비게이션: pick 기본 하단 탭 바 / 중앙 플로팅 반원 메뉴.
Stored device-local via @AppStorage(SettingsKeys.navStyle) (not synced);
the 탭바 구성 section auto-hides in radial mode since all tabs are shown.
- iPhone (Compact) only: RootNavigationView branches on NavStyle. iPad/Mac
sidebar and the watch target are untouched.
- Radial menu (RadialNavigationView): glass FAB at bottom-center over a
tab-bar-hidden TabView (per-tab navigation state preserved). Tapping the
FAB blooms all 7 tabs into an upper 180° arc with per-icon name labels;
tapping an icon routes immediately and closes; tapping the FAB or the
dimming scrim closes. Current tab is highlighted.
- Drops the 더보기 indirection in radial mode via AppRouter.allTabsDirect,
so cross-tab routing (openHistory/openStats) selects tabs directly.
- Liquid Glass (.ultraThinMaterial) styling on the FAB, icons and labels.
- Overlay is kept top-most in the ZStack with hit-testing gated to the
expanded state, so scrolling grid content can never intercept the button.
- DEBUG: -navStyle radial / -radialExpanded YES launch args for simulator
verification (no way to tap widgets/overlays via simctl).
Scope: iPhone navigation only; no changes to iPad/Mac sidebar, the watch
app, core data/models, or widget code.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- IntentStore now recreates the ModelContainer on demand in the widget
extension (IntentStore.refresh()), called before every AppIntent
perform() and every provider's makeEntry(). Fixes stale/blank widget
buttons and double-toggle bugs caused by the extension process
reading an old in-memory snapshot after CloudKit remote changes.
- Replaced the fixed 15/30-minute polling Timeline policy (which burns
WidgetKit's daily reload budget across 6 widget kinds) with
WidgetRefresh.timeline: idle widgets wait until the next logical-day
boundary since value changes are already pushed via
reloadAllTimelines, while widgets showing a live ratio (a currently
running quest/goal) get pre-computed future entries so progress
rings and bars keep advancing without extra reloads.
- Removed the per-widget "위젯 테마" (match app / light / dark / liquid
glass) configuration option from all 5 home-screen widgets. It
duplicated the same enum five times, and the liquid-glass variant's
custom background/scheme override fought the system's own
vibrant/tinted rendering in tinted Home Screens, which was part of
the "블랙 화면"/렌더링 오류 reports. Widgets now simply follow the
app's own theme setting (WidgetAppearance.appScheme), which is what
"앱 테마와 일치" already defaulted to.
- Added shared gallery/placeholder sample data (ActionCellSnapshot,
QuestCellSnapshot, GoalSnapshot, StatsChart sample points) so the
widget gallery and redacted placeholders show a realistic shape
instead of an empty/blank card.
- Unified empty-state UI via WidgetEmptyView across all 5 widgets.
Scope: Widgets/*, Shared/HaruDanimIntents.swift (widget-facing
IntentStore only), IOS/Views/WidgetPreviewScreen.swift (DEBUG preview
tool). No changes to app core data models, sync, or view logic.
위젯 동기화 크래시 수정:
- DataStore: CloudKit 미러링을 메인 앱 프로세스 전담으로 분리.
위젯 등 앱 확장(.appex)은 CloudKit 엔타이틀먼트가 없고 메모리·수명
제약으로 미러링 설정이 크래시/행을 유발하므로, 확장 프로세스는 항상
같은 App Group 스토어 파일을 로컬 전용(cloudKitDatabase: .none)으로
연다. 확장이 쓴 변경은 메인 앱이 히스토리로 집어 CloudKit에 내보냄
- CloudSyncMonitor 신설: 메인 앱이 NSPersistentStoreRemoteChange
(다른 기기의 CloudKit 가져오기·위젯 인텐트의 쓰기)를 디바운스로 받아
라이브 액티비티 → 위젯 타임라인 → 워치 스냅숏 순으로 즉시 갱신
- 인터랙티브 위젯(AppIntent)의 컨테이너 매핑 점검: IntentStore.commit이
저장 직후 reloadAllTimelines를 이미 보장함을 확인·문서화
모음 탭 목표 진행 현황 카드 개선:
- '다짐별로 보기'의 상위 3개 제한 제거 — 모든 다짐 확인 가능
- 화면 균형을 위해 접힘/펼침 UX 적용: iPhone은 3개, iPad는 6개까지
접힌 상태로 보여주고 '다짐 N개 더 보기' 버튼으로 카드 안에서 펼침
(카드 탭 이동과 충돌하지 않는 독립 버튼, 스냅 애니메이션)
- DebugSeed: 접힘/펼침 검증용 4번째 다짐 추가, -expandGoalCard 인자 추가
검증: iPhone 17 Pro·iPad Pro 11 시뮬레이터에서 접힘/펼침 렌더링과
사이드바 레이아웃 확인, 전체 타깃(iOS·워치·위젯) 빌드 성공
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- Add Shared/LocalPrefs.swift: device-local UI preferences stored in App Group
UserDefaults (never synced by iCloud), read identically by app, widgets,
watch snapshot, and App Intents on the same device
- Move to local storage: main-tab action button order (UUID array), pinned
goal-progress cards on the main tab, goal quest-list collapse state, and
per-action memo-prompt popup — reordering or repinning on iPhone no longer
touches the iPad and vice versa
- Defense logic: actions not in the local order list (e.g. newly received via
CloudKit from another device) are automatically appended to the end, sorted
by legacy sortOrder then creation date
- One-time migration adopts existing @Model values (sortOrder, showsOnMain,
isCollapsed, promptsForNote) into local prefs at launch; model properties
stay in place so the CloudKit schema is untouched and records keep syncing
- Data remains fully synced: actions, goals, quests, sessions, count entries,
statistics, and goal/tag/quest list order are untouched by this change
- Verified in simulator: legacy data migrates losslessly, grid renders from
local order, unknown action lands at the end; iOS+watch+widgets build clean
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- 목표 하루 진행률은 오늘이 수행일인 다짐만 모수로 계산 (요일·날짜 지정으로
오늘 쉬는 다짐은 분모에서 제외, QuestProgress.isScheduled 신설)
- 오늘 수행할 다짐이 하나도 없는 날은 0% 대신 100%(완료 상태)로 표기
- 주간/월간 진행률은 기존대로 전체 다짐 기준 유지
- 이벤트 기반 위젯 새로 고침: 앱 내 모든 핵심 Mutation(측정 시작/종료,
횟수 추가, 기록 추가·수정·삭제, 행동·꼬리표·목표·다짐 CRUD, 순서 변경,
목표 상태 변경)의 끝단에서 DataChange.commit() 호출 — 저장 즉시 확정 후
Live Activity·위젯 타임라인·워치 스냅숏을 한 번에 동기화
- 주기적 백그라운드 새로 고침 없이 변경 시점에만 갱신해 WidgetKit
업데이트 예산 절약 (타이머 표시는 기존 Text(style: .timer)로 시스템이
자체 렌더링하므로 타임라인 소모 없음)
- 하루 시작 시간·주 시작 요일 설정 변경 시에도 위젯 집계 갱신
- 시뮬레이터 검증: 월·수·금 다짐이 있는 목표의 토요일 하루 진행률이
33%가 아닌 50%(오늘 수행 다짐 2개 평균)로 표시됨 확인
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
라이브 액티비티(다이내믹 아일랜드) 워치 연동:
- 워치 실행 명령이 세션 활성화 전이거나 sendMessage 실패 시 유실되던 문제를
transferUserInfo 큐 폴백으로 보강 (iPhone didReceiveUserInfo 수신 처리 추가)
- 큐로 지연 도착한 실행 명령이 엉뚱한 토글을 일으키지 않도록 5분 유효 시간 부여
- 백그라운드에서 Live Activity 시작이 거부되면 pendingStartRetry로 기억했다가
앱 포그라운드 복귀(scenePhase active) 시 재시도 + DB 상태 재동기화
- WatchSync 진단용 os_log(notice) 추가, DEBUG -autoRunFirstAction 검증 훅 추가
- 시뮬레이터 검증: 워치 시작 → 아이폰 세션 시작 + 다이내믹 아일랜드 생성,
워치 종료 → 세션 종료 + 아일랜드 소멸 확인 (큐 폴백 채널은 시뮬레이터
transferUserInfo 한계로 실기기 확인 필요)
앱 이름 전역 띄어쓰기 ('하루다님' → '하루 다님'):
- pbxproj CFBundleDisplayName 6곳, InfoPlist.xcstrings 3개(ko), 스플래시(ContentView),
워치 내비게이션 타이틀, 컴플리케이션 문구, 문자열 카탈로그 키 일괄 변경
- 워치 카탈로그 영문 값 HaruDanim → Haru Danim 통일
통계 위젯 X축 라벨 겹침 방지:
- 날짜 축 포인트가 7개를 초과하면(한 달·일별 등) 라벨을 숨기고 눈금만 표시
위젯 설정 '선택 안 함' 옵션:
- Action/Goal/Quest 엔티티에 zero-UUID 센티널 추가, 제안 목록 최상단 노출
- 2·3번째 슬롯 선택을 되돌릴 수 있고, 위젯 조회 단계에서 자연스럽게 걸러짐
- ko/en/ja 번역 추가 (None / 選択しない)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- 언어 설정 실적용: 변경 시 AppleLanguages 오버라이드 저장, 재시작 안내 footer
- 자동 추출되지 않던 일반 문자열(탭 이름, 픽커 라벨, 상태 뱃지, 포매터,
삼항 문구, 심볼 카테고리 등)을 String(localized:)로 전환
- 위젯·워치 앱·컴플리케이션 타깃에 Localizable.xcstrings 신설
(중복 리소스 방지 pbxproj 예외 추가), xcstringstool sync로 키 추출
- 4개 카탈로그 전체 키(고유 430개)에 en/ja 번역 주입
- 영어의 긴 횟수 단위("times")가 잘리지 않도록 모음 탭 셀 축소 허용
- 검증: -AppleLanguages "(en)"/"(ja)"로 모음 탭·설정 화면 확인
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- StoreKitEntitlementProvider: Transaction.currentEntitlements 기반 실권한
조회 + Transaction.updates 이벤트 반영. 앱 시작 시 PremiumManager.bootstrap
으로 주입 (확장 타깃은 기존 PremiumGate 캐시 그대로)
- PremiumStore: 상품 로드/구매/복원 (월·년 구독 + 평생 이용권 3종)
- PremiumView: 실구매 UI (상품 목록·가격·구매 복원, 미로드 시 재시도 폴백)
- DEBUG 강제 프리미엄(-premium, 테스트 토글) 중에는 StoreKit 결과가
덮어쓰지 않도록 보호
- HaruDanim.storekit 로컬 테스트 구성(월 5,000 / 년 50,000 / 일회성 80,000원,
ko·en·ja 현지화) + 메인 스킴에 연결 → Xcode 실행으로 결제 흐름 테스트 가능
- 남은 것: App Store Connect에 productID 3종 등록만 하면 실결제 연결 완료
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- AppGroup.defaults를 단일 인스턴스(static let)로 변경: 접근마다 새
UserDefaults를 만들어 @AppStorage(store:) 관찰이 끊기던 문제 수정
→ 설정에서 테마를 바꾸면 재시작 없이 즉시 적용됨
- 위젯 containerBackground에 강제 컬러 스킴을 직접 지정: 시스템이
배경을 위젯 환경으로 그려 라이트/다크 고정·앱 테마 일치가 실제
위젯에서 무시되던 문제 수정
- 테마 변경 시 위젯 타임라인 갱신 (앱 테마와 일치 옵션 즉시 반영)
- 리퀴드 글라스 실구현: 머티리얼+하이라이트 배경(GlassWidgetBackground),
유리 테두리 셀(WidgetCellBackground), 색이 비치는 틴트 셀
(WidgetTintedCellBackground, 행동 실행 위젯 적용)
- 검증 인자 -themeAutoToggle YES 추가 (3초 후 테마 반전)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- 홈 위젯 5종(행동 실행/목표 진행률/다짐 진행률/다짐 현황/통계) 설정에
'위젯 테마' 옵션 추가
- 앱 테마와 일치: 앱의 라이트/다크 설정을 따름 (테마 키를 App Group으로 이관해
위젯이 읽을 수 있게 함)
- 라이트/다크 고정: 컬러 스킴 강제 + 해당 모드 팔레트
- 리퀴드 글라스: 반투명 머티리얼 배경 + 표면 셀도 머티리얼로 변형해
유리 질감 위에서 가독성 유지
- DEBUG 미리보기가 테마를 재현하도록 개선 (-widgetTheme light|dark|glass,
글라스는 그라데이션 배경 위에 렌더링)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
- DataStore: App Group 컨테이너의 HaruDanim.store 사용, 기존 default.store 1회 자동 이관
- 프리미엄 + 동기화 토글 시 CloudKit(.private) 구성으로 컨테이너 생성, 실패하면 로컬 폴백
- CloudKit 요건(모든 관계 optional)에 맞춰 to-many 관계를 optional 저장 + 기존 이름
접근자로 리팩터링 (originalName 매핑으로 기존 데이터 유지)
- Tag/Action/Goal/Quest에 uuid 추가 (시리 단축어·위젯·워치 식별자) + 중복 보정
- 모델 레이어(Models/Settings/DayMath/QuestProgress/Formatters/Theme)를 Shared/로 이동
— 위젯 확장과 공유
- 집계 설정(주 시작 요일·하루 시작 시간·대표 시간·짧은 기록 무시)을 App Group defaults로
이관해 위젯·워치와 공유, 설정의 '기기 간 동기화' 토글 활성화
- Goal.combinedSpanRatio 공용화 (모음 탭 카드·위젯·시리가 동일 산식 사용)
- DEBUG 검증 인자: -cloudSync YES, -settingsScrollPremium YES
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7